Skip to content

Commit ac05a66

Browse files
committed
[Diagnostics] Introduce "Educational Notes" for diagnostics
Educational notes are small pieces of documentation which explain a concept relevant to some diagnostic message. If -enable-descriptive-diagnostics is passed, they will be printed after a diagnostic message if available. Educational notes can be found at /usr/share/doc/diagnostics in a toolchain, and are associated with specific compiler diagnostics in EducationalNotes.def.
1 parent 9931aab commit ac05a66

14 files changed

+121
-1
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,8 @@ endif()
11291129

11301130
add_subdirectory(utils)
11311131

1132+
add_subdirectory(userdocs)
1133+
11321134
if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
11331135
if(SWIFT_BUILD_PERF_TESTSUITE)
11341136
add_subdirectory(benchmark)

include/swift/AST/DiagnosticConsumer.h

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ struct DiagnosticInfo {
5757
/// DiagnosticInfo of notes which are children of this diagnostic, if any
5858
ArrayRef<DiagnosticInfo *> ChildDiagnosticInfo;
5959

60+
/// Paths to "educational note" diagnostic documentation in the toolchain.
61+
ArrayRef<std::string> EducationalNotePaths;
62+
6063
/// Represents a fix-it, a replacement of one range of text with another.
6164
class FixIt {
6265
CharSourceRange Range;

include/swift/AST/DiagnosticEngine.h

+10
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,9 @@ namespace swift {
673673
/// Use descriptive diagnostic style when available.
674674
bool useDescriptiveDiagnostics = false;
675675

676+
/// Path to diagnostic documentation directory.
677+
std::string diagnosticDocumentationPath = "";
678+
676679
friend class InFlightDiagnostic;
677680
friend class DiagnosticTransaction;
678681
friend class CompoundDiagnosticTransaction;
@@ -723,6 +726,13 @@ namespace swift {
723726
return useDescriptiveDiagnostics;
724727
}
725728

729+
void setDiagnosticDocumentationPath(std::string path) {
730+
diagnosticDocumentationPath = path;
731+
}
732+
StringRef getDiagnosticDocumentationPath() {
733+
return diagnosticDocumentationPath;
734+
}
735+
726736
void ignoreDiagnostic(DiagID id) {
727737
state.setDiagnosticBehavior(id, DiagnosticState::Behavior::Ignore);
728738
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===-- EducationalNotes.def - Diagnostic Documentation Content -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file associates diagnostics with relevant educational notes which
14+
// explain important concepts.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef EDUCATIONAL_NOTES
19+
# error Must define EDUCATIONAL_NOTES
20+
#endif
21+
22+
// EDUCATIONAL_NOTES(DIAG_ID, EDUCATIONAL_NOTE_FILENAMES...)
23+
24+
EDUCATIONAL_NOTES(non_nominal_no_initializers, "nominal-types.md")
25+
EDUCATIONAL_NOTES(non_nominal_extension, "nominal-types.md")
26+
EDUCATIONAL_NOTES(associated_type_witness_conform_impossible,
27+
"nominal-types.md")
28+
29+
#undef EDUCATIONAL_NOTES

include/swift/Basic/DiagnosticOptions.h

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class DiagnosticOptions {
5959
/// Descriptive diagnostic output is not intended to be machine-readable.
6060
bool EnableDescriptiveDiagnostics = false;
6161

62+
std::string DiagnosticDocumentationPath = "";
63+
6264
/// Return a hash code of any components from these options that should
6365
/// contribute to a Swift Bridging PCH hash.
6466
llvm::hash_code getPCHHashComponents() const {

include/swift/Option/FrontendOptions.td

+4
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ def show_diagnostics_after_fatal : Flag<["-"], "show-diagnostics-after-fatal">,
115115

116116
def enable_descriptive_diagnostics : Flag<["-"], "enable-descriptive-diagnostics">,
117117
HelpText<"Show descriptive diagnostic information, if available.">;
118+
119+
def diagnostic_documentation_path
120+
: Separate<["-"], "diagnostic-documentation-path">, MetaVarName<"<path>">,
121+
HelpText<"Path to diagnostic documentation resources">;
118122

119123
def enable_swiftcall : Flag<["-"], "enable-swiftcall">,
120124
HelpText<"Enable the use of LLVM swiftcall support">;

lib/AST/DiagnosticEngine.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ static constexpr const char *const fixItStrings[] = {
116116
"<not a fix-it>",
117117
};
118118

119+
#define EDUCATIONAL_NOTES(DIAG, ...) \
120+
static constexpr const char *const DIAG##_educationalNotes[] = {__VA_ARGS__, \
121+
nullptr};
122+
#include "swift/AST/EducationalNotes.def"
123+
124+
static constexpr const char *const *educationalNotes[LocalDiagID::NumDiags]{
125+
[LocalDiagID::invalid_diagnostic] = {},
126+
#define EDUCATIONAL_NOTES(DIAG, ...) \
127+
[LocalDiagID::DIAG] = DIAG##_educationalNotes,
128+
#include "swift/AST/EducationalNotes.def"
129+
};
130+
119131
DiagnosticState::DiagnosticState() {
120132
// Initialize our per-diagnostic state to default
121133
perDiagnosticBehavior.resize(LocalDiagID::NumDiags, Behavior::Unspecified);
@@ -951,6 +963,19 @@ void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
951963
}
952964
}
953965
info->ChildDiagnosticInfo = childInfoPtrs;
966+
967+
SmallVector<std::string, 1> educationalNotePaths;
968+
if (useDescriptiveDiagnostics) {
969+
auto associatedNotes = educationalNotes[(uint32_t)diagnostic.getID()];
970+
while (associatedNotes && *associatedNotes) {
971+
SmallString<128> notePath(getDiagnosticDocumentationPath());
972+
llvm::sys::path::append(notePath, *associatedNotes);
973+
educationalNotePaths.push_back(notePath.str());
974+
associatedNotes++;
975+
}
976+
info->EducationalNotePaths = educationalNotePaths;
977+
}
978+
954979
for (auto &consumer : Consumers) {
955980
consumer->handleDiagnostic(SourceMgr, *info);
956981
}

lib/Frontend/CompilerInvocation.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ void CompilerInvocation::setMainExecutablePath(StringRef Path) {
4444
llvm::sys::path::remove_filename(LibPath); // Remove /bin
4545
llvm::sys::path::append(LibPath, "lib", "swift");
4646
setRuntimeResourcePath(LibPath.str());
47+
48+
llvm::SmallString<128> DiagnosticDocsPath(Path);
49+
llvm::sys::path::remove_filename(DiagnosticDocsPath); // Remove /swift
50+
llvm::sys::path::remove_filename(DiagnosticDocsPath); // Remove /bin
51+
llvm::sys::path::append(DiagnosticDocsPath, "share", "doc", "swift",
52+
"diagnostics");
53+
DiagnosticOpts.DiagnosticDocumentationPath = DiagnosticDocsPath.str();
4754
}
4855

4956
/// If we haven't explicitly passed -prebuilt-module-cache-path, set it to
@@ -682,7 +689,9 @@ static bool ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args,
682689
Opts.PrintDiagnosticNames |= Args.hasArg(OPT_debug_diagnostic_names);
683690
Opts.EnableDescriptiveDiagnostics |=
684691
Args.hasArg(OPT_enable_descriptive_diagnostics);
685-
692+
if (Arg *A = Args.getLastArg(OPT_diagnostic_documentation_path)) {
693+
Opts.DiagnosticDocumentationPath = A->getValue();
694+
}
686695
assert(!(Opts.WarningsAsErrors && Opts.SuppressWarnings) &&
687696
"conflicting arguments; should have been caught by driver");
688697

lib/Frontend/Frontend.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ void CompilerInstance::setUpDiagnosticOptions() {
319319
if (Invocation.getDiagnosticOptions().EnableDescriptiveDiagnostics) {
320320
Diagnostics.setUseDescriptiveDiagnostics(true);
321321
}
322+
Diagnostics.setDiagnosticDocumentationPath(
323+
Invocation.getDiagnosticOptions().DiagnosticDocumentationPath);
322324
}
323325

324326
// The ordering of ModuleLoaders is important!

lib/Frontend/PrintingDiagnosticConsumer.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ void PrintingDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
6969
return;
7070

7171
printDiagnostic(SM, Info);
72+
for (auto path : Info.EducationalNotePaths) {
73+
auto buffer = llvm::MemoryBuffer::getFile(path);
74+
if (buffer) {
75+
Stream << buffer->get()->getBuffer() << "\n";
76+
}
77+
}
7278

7379
for (auto ChildInfo : Info.ChildDiagnosticInfo) {
7480
printDiagnostic(SM, *ChildInfo);
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: not %target-swift-frontend -enable-descriptive-diagnostics -diagnostic-documentation-path %S/test-docs/ -typecheck %s 2>&1 | %FileCheck %s
2+
3+
// A diagnostic with no educational notes
4+
let x = 1 +
5+
// CHECK: error: expected expression after operator
6+
// CHECK-NOT: {{-+$}}
7+
8+
// A diagnostic with an educational note
9+
extension (Int, Int) {}
10+
// CHECK: error: non-nominal type '(Int, Int)' cannot be extended
11+
// CHECK-NEXT: extension (Int, Int) {}
12+
// CHECK-NEXT: ^ ~~~~~~~~~~
13+
// CHECK-NEXT: Nominal Types
14+
// CHECK-NEXT: -------------
15+
// CHECK-NEXT: Nominal types documentation content
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Nominal Types
2+
-------------
3+
Nominal types documentation content

userdocs/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
swift_install_in_component(DIRECTORY diagnostics
2+
DESTINATION "share/doc/swift"
3+
COMPONENT compiler)

userdocs/diagnostics/nominal-types.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Nominal types
2+
-------------
3+
In Swift, a type is considered a nominal type if it is named. In other words, it has been defined by declaring the type somewhere in code. Examples of nominal types include classes, structs and enums, all of which must be declared before using them. Nominal types are an important concept in Swift because they can be extended, explicitly initialized using the 'MyType()' syntax, and may conform to protocols.
4+
5+
In contrast, non-nominal types have none of these capabilities. A non-nominal type is any type which is not nominal. They are sometimes called ”structural types” because they are usually obtained by composing other types. Examples include function types like '(Int) -> (String)', tuple types like '(Int, String)', metatypes like 'Int.Type', and special types like 'Any' and 'AnyObject'.
6+
7+
Whether the name of a protocol refers to a nominal or non-nominal type depends on where it appears in code. When used in a declaration or extension like 'extension MyProtocol { … }', 'MyProtocol' refers to a protocol type, which is nominal. This means that it may be extended and conform to protocols. However, when written as the type of a constant or variable, MyProtocol instead refers to a non-nominal, existential type. As a result, code like 'let value: MyProtocol = MyProtocol()' is not allowed because 'MyProtocol' refers to a non-nominal type in this context and cannot be explicitly initialized.

0 commit comments

Comments
 (0)