mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
425cd0ebd7
**Overview**: This PR introduces the basic infrastructure needed to eventually migrate derived macros generation from hand-crafted AST nodes to macros. No conformance have been migrated yet. **Motivation**: Derived conformances (e.g. `Equatable`, `Hashable`, `Codable`, ...) are currently implemented as a special case in the compiler, producing synthetic AST nodes directly. Migrating this to macros will hopefully unify the code path with the existing macro expansion infrastructure, make conformance synthesis easier to extend and test as well as reducing the amount of special cases in the compiler. **Changes**: - New experimental feature flag `DeriveConformancesViaMacros`: Introduces the flag that will eventually gate the new derived conformance code paths. It does not control any behaviour for the moment as none have been migrated yet but this enables future changes to be built incrementally. - New GeneratedSourceInfo and SourceFile kinds `SyntheticMacro`: Introduces new GSI and SourceFile kinds named `SyntheticMacro` to represent macros synthesized by the compiler. Since macros need a real buffer to expand, this is the kind of source file and GSI associated with those buffers. - Conformance derivation via macros API: Introduces the `deriveRequirementViaMacro` function that produces the required witness via macro expansion. See https://github.com/swiftlang/llvm-project/pull/13124 for llvm-related changes. **Next steps**: - Macros do not contain any semantic information, especially regarding types. Therefore it is necessary to provide them with type information as an argument so they can eventually derive the conformances. A separate PR is being created to generate this type information as strings containing swift-parsable code for easy parsing on the macro end. - Implement derived conformance synthesis for individual protocols using the new infrastructure, like `Equatable` or `Hashable` for starters. - Wire the experimental flag to gate the new path once an implementation exists --------- Co-authored-by: Hamish Knight <hamish_knight@apple.com>
1956 lines
68 KiB
C++
1956 lines
68 KiB
C++
//===--- DiagnosticEngine.cpp - Diagnostic Display Engine -----------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the DiagnosticEngine class, which manages any diagnostics
|
|
// emitted by Swift.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/DiagnosticEngine.h"
|
|
#include "swift/AST/AvailabilityDomain.h"
|
|
#include "swift/AST/ASTBridging.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/ASTPrinter.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/DiagnosticGroups.h"
|
|
#include "swift/AST/DiagnosticList.h"
|
|
#include "swift/AST/DiagnosticSuppression.h"
|
|
#include "swift/AST/DiagnosticsCommon.h"
|
|
#include "swift/AST/DiagnosticsFrontend.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/Module.h"
|
|
#include "swift/AST/Pattern.h"
|
|
#include "swift/AST/ParseRequests.h"
|
|
#include "swift/AST/PrintOptions.h"
|
|
#include "swift/AST/SourceFile.h"
|
|
#include "swift/AST/Stmt.h"
|
|
#include "swift/AST/TypeCheckRequests.h"
|
|
#include "swift/AST/TypeRepr.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/Bridging/ASTGen.h"
|
|
#include "swift/Config.h"
|
|
#include "swift/Localization/LocalizationFormat.h"
|
|
#include "swift/Parse/Lexer.h" // bad dependency
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/PrettyPrinter.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/Twine.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
using namespace swift;
|
|
|
|
static_assert(IsTriviallyDestructible<ZeroArgDiagnostic>::value,
|
|
"ZeroArgDiagnostic is meant to be trivially destructable");
|
|
|
|
namespace {
|
|
enum class DiagnosticOptions {
|
|
/// No options.
|
|
none,
|
|
|
|
/// The location of this diagnostic points to the beginning of the first
|
|
/// token that the parser considers invalid. If this token is located at the
|
|
/// beginning of the line, then the location is adjusted to point to the end
|
|
/// of the previous token.
|
|
///
|
|
/// This behavior improves experience for "expected token X" diagnostics.
|
|
PointsToFirstBadToken,
|
|
|
|
/// After a fatal error subsequent diagnostics are suppressed.
|
|
Fatal,
|
|
|
|
/// An API or ABI breakage diagnostic emitted by the API digester.
|
|
APIDigesterBreakage,
|
|
|
|
/// A deprecation warning or error.
|
|
Deprecation,
|
|
|
|
/// A diagnostic warning about an unused element.
|
|
NoUsage,
|
|
};
|
|
struct StoredDiagnosticInfo {
|
|
DiagnosticKind kind : 2;
|
|
bool pointsToFirstBadToken : 1;
|
|
bool isFatal : 1;
|
|
bool isAPIDigesterBreakage : 1;
|
|
bool isDeprecation : 1;
|
|
bool isNoUsage : 1;
|
|
DiagGroupID groupID;
|
|
|
|
constexpr StoredDiagnosticInfo(DiagnosticKind k, bool firstBadToken,
|
|
bool fatal, bool isAPIDigesterBreakage,
|
|
bool deprecation, bool noUsage,
|
|
DiagGroupID groupID)
|
|
: kind(k), pointsToFirstBadToken(firstBadToken), isFatal(fatal),
|
|
isAPIDigesterBreakage(isAPIDigesterBreakage),
|
|
isDeprecation(deprecation), isNoUsage(noUsage),
|
|
groupID(groupID) {}
|
|
constexpr StoredDiagnosticInfo(DiagnosticKind k, DiagnosticOptions opts,
|
|
DiagGroupID groupID)
|
|
: StoredDiagnosticInfo(k,
|
|
opts == DiagnosticOptions::PointsToFirstBadToken,
|
|
opts == DiagnosticOptions::Fatal,
|
|
opts == DiagnosticOptions::APIDigesterBreakage,
|
|
opts == DiagnosticOptions::Deprecation,
|
|
opts == DiagnosticOptions::NoUsage,
|
|
groupID) {}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
// TODO: categorization
|
|
static const constexpr StoredDiagnosticInfo storedDiagnosticInfos[] = {
|
|
#define GROUPED_ERROR(ID, Group, Options, Text, Signature) \
|
|
StoredDiagnosticInfo(DiagnosticKind::Error, DiagnosticOptions::Options, \
|
|
DiagGroupID::Group),
|
|
#define GROUPED_WARNING(ID, Group, Options, Text, Signature) \
|
|
StoredDiagnosticInfo(DiagnosticKind::Warning, DiagnosticOptions::Options, \
|
|
DiagGroupID::Group),
|
|
#define NOTE(ID, Options, Text, Signature) \
|
|
StoredDiagnosticInfo(DiagnosticKind::Note, DiagnosticOptions::Options, \
|
|
DiagGroupID::no_group),
|
|
#define REMARK(ID, Options, Text, Signature) \
|
|
StoredDiagnosticInfo(DiagnosticKind::Remark, DiagnosticOptions::Options, \
|
|
DiagGroupID::no_group),
|
|
#include "swift/AST/DiagnosticsAll.def"
|
|
};
|
|
static_assert(sizeof(storedDiagnosticInfos) / sizeof(StoredDiagnosticInfo) ==
|
|
NumDiagIDs,
|
|
"array size mismatch");
|
|
|
|
static constexpr const char * const diagnosticStrings[] = {
|
|
#define DIAG(KIND, ID, Group, Options, Text, Signature) Text,
|
|
#include "swift/AST/DiagnosticsAll.def"
|
|
"<not a diagnostic>",
|
|
};
|
|
|
|
static constexpr const char *const diagnosticIDStrings[] = {
|
|
#define DIAG(KIND, ID, Group, Options, Text, Signature) #ID,
|
|
#include "swift/AST/DiagnosticsAll.def"
|
|
"<not a diagnostic>",
|
|
};
|
|
|
|
static constexpr const char *const fixItStrings[] = {
|
|
#define DIAG(KIND, ID, Group, Options, Text, Signature)
|
|
#define FIXIT(ID, Text, Signature) Text,
|
|
#include "swift/AST/DiagnosticsAll.def"
|
|
"<not a fix-it>",
|
|
};
|
|
|
|
DiagnosticState::DiagnosticState() {
|
|
// Initialize ignored diagnostic groups to defaults
|
|
for (const auto &groupInfo : diagnosticGroupsInfo)
|
|
if (groupInfo.defaultIgnoreWarnings)
|
|
getDiagGroupInfoByID(groupInfo.id).traverseDepthFirst([&](auto group) {
|
|
addWarningGroupControl(group.id, WarningGroupBehavior::Ignored);
|
|
});
|
|
|
|
// Initialize compilerIgnoredDiagnostics
|
|
compilerIgnoredDiagnostics.resize(NumDiagIDs);
|
|
}
|
|
|
|
bool DiagnosticState::isIgnoredDiagnosticGroupTree(DiagGroupID id) const {
|
|
bool anyEnabled = false;
|
|
getDiagGroupInfoByID(id).traverseDepthFirst([&](auto group) {
|
|
anyEnabled |=
|
|
!warningGroupBehaviorMap.contains(group.id) ||
|
|
warningGroupBehaviorMap.find(group.id)->second.getBehavior() !=
|
|
WarningGroupBehavior::Ignored;
|
|
});
|
|
return !anyEnabled;
|
|
}
|
|
|
|
Diagnostic::Diagnostic(DiagID ID)
|
|
: Diagnostic(ID, storedDiagnosticInfos[(unsigned)ID].groupID) {}
|
|
|
|
std::optional<const DiagnosticInfo *> Diagnostic::getWrappedDiagnostic() const {
|
|
for (const auto &arg : getArgs()) {
|
|
if (arg.getKind() == DiagnosticArgumentKind::Diagnostic) {
|
|
return arg.getAsDiagnostic();
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
SourceLoc Diagnostic::getLocOrDeclLoc() const {
|
|
if (auto loc = getLoc())
|
|
return loc;
|
|
|
|
if (auto *D = getDecl())
|
|
return D->getLoc();
|
|
|
|
return SourceLoc();
|
|
}
|
|
|
|
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceRange SR) {
|
|
return CharSourceRange(SM, SR.Start, Lexer::getLocForEndOfToken(SM, SR.End));
|
|
}
|
|
|
|
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceLoc Start,
|
|
SourceLoc End) {
|
|
return CharSourceRange(SM, Start, End);
|
|
}
|
|
|
|
/// Extract a character at \p Loc. If \p Loc is the end of the buffer,
|
|
/// return '\f'.
|
|
static char extractCharAfter(SourceManager &SM, SourceLoc Loc) {
|
|
auto chars = SM.extractText({Loc, 1});
|
|
return chars.empty() ? '\f' : chars[0];
|
|
}
|
|
|
|
/// Extract a character immediately before \p Loc. If \p Loc is the
|
|
/// start of the buffer, return '\f'.
|
|
static char extractCharBefore(SourceManager &SM, SourceLoc Loc) {
|
|
// We have to be careful not to go off the front of the buffer.
|
|
auto bufferID = SM.findBufferContainingLoc(Loc);
|
|
auto bufferRange = SM.getRangeForBuffer(bufferID);
|
|
if (bufferRange.getStart() == Loc)
|
|
return '\f';
|
|
auto chars = SM.extractText({Loc.getAdvancedLoc(-1), 1}, bufferID);
|
|
assert(!chars.empty() && "Couldn't extractText with valid range");
|
|
return chars[0];
|
|
}
|
|
|
|
detail::ActiveDiagnostic &InFlightDiagnostic::getActiveDiag() const {
|
|
return Engine->getActiveDiagnostic(*this);
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::highlight(SourceRange R) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (Engine && R.isValid())
|
|
getDiag().addRange(toCharSourceRange(Engine->SourceMgr, R));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::highlightChars(SourceLoc Start,
|
|
SourceLoc End) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (Engine && Start.isValid())
|
|
getDiag().addRange(toCharSourceRange(Engine->SourceMgr, Start, End));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::highlightChars(CharSourceRange Range) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (Engine && Range.getStart().isValid())
|
|
getDiag().addRange(Range);
|
|
return *this;
|
|
}
|
|
|
|
/// Add an insertion fix-it to the currently-active diagnostic. The
|
|
/// text is inserted immediately *after* the token specified.
|
|
///
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::fixItInsertAfter(SourceLoc L, StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
L = Lexer::getLocForEndOfToken(Engine->SourceMgr, L);
|
|
return fixItInsert(L, FormatString, Args);
|
|
}
|
|
|
|
/// Add a token-based removal fix-it to the currently-active
|
|
/// diagnostic.
|
|
InFlightDiagnostic &InFlightDiagnostic::fixItRemove(SourceRange R) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (R.isInvalid() || !Engine) return *this;
|
|
|
|
// Convert from a token range to a CharSourceRange, which points to the end of
|
|
// the token we want to remove.
|
|
auto &SM = Engine->SourceMgr;
|
|
auto charRange = toCharSourceRange(SM, R);
|
|
auto charAfter = extractCharAfter(SM, charRange.getEnd());
|
|
|
|
// If we're removing something (e.g. a keyword or an attribute), do a bit of
|
|
// extra work to make sure that we leave the code in a good place, without
|
|
// extraneous white space around its hole:
|
|
//
|
|
// - If there's a newline immediately following the range, check whether the
|
|
// content of the line leading up to the range is all whitespace. Remove
|
|
// the entire line if so.
|
|
// - If there is a space before and after the end of range, nuke the
|
|
// space afterward to keep things consistent.
|
|
if (charAfter == '\n' || charAfter == '\r') {
|
|
auto lineStartLoc = Lexer::getLocForStartOfLine(SM, charRange.getStart());
|
|
auto lineStart = SM.extractText(
|
|
toCharSourceRange(SM, lineStartLoc, charRange.getStart()));
|
|
if (lineStart.ltrim().empty()) {
|
|
charRange = CharSourceRange(
|
|
lineStartLoc, lineStart.size() + charRange.getByteLength() + 1);
|
|
}
|
|
} else if (charAfter == ' ' &&
|
|
isspace(extractCharBefore(SM, charRange.getStart()))) {
|
|
charRange = CharSourceRange(charRange.getStart(),
|
|
charRange.getByteLength()+1);
|
|
}
|
|
getDiag().addFixIt(Diagnostic::FixIt(charRange, {}, {}));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::fixItReplace(SourceRange R, StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
auto &SM = Engine->SourceMgr;
|
|
auto charRange = toCharSourceRange(SM, R);
|
|
|
|
getDiag().addFixIt(Diagnostic::FixIt(charRange, FormatString, Args));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::fixItReplace(SourceRange R,
|
|
StringRef Str) {
|
|
if (Str.empty())
|
|
return fixItRemove(R);
|
|
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (R.isInvalid() || !Engine) return *this;
|
|
|
|
auto &SM = Engine->SourceMgr;
|
|
auto charRange = toCharSourceRange(SM, R);
|
|
|
|
// If we're replacing with something that wants spaces around it, do a bit of
|
|
// extra work so that we don't suggest extra spaces.
|
|
// FIXME: This could probably be applied to structured fix-its as well.
|
|
if (Str.back() == ' ') {
|
|
if (isspace(extractCharAfter(SM, charRange.getEnd())))
|
|
Str = Str.drop_back();
|
|
}
|
|
if (!Str.empty() && Str.front() == ' ') {
|
|
if (isspace(extractCharBefore(SM, charRange.getStart())))
|
|
Str = Str.drop_front();
|
|
}
|
|
|
|
return fixItReplace(R, "%0", {Str});
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::fixItReplaceChars(SourceLoc Start, SourceLoc End,
|
|
StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
if (Engine && Start.isValid())
|
|
getDiag().addFixIt(Diagnostic::FixIt(
|
|
toCharSourceRange(Engine->SourceMgr, Start, End), FormatString, Args));
|
|
return *this;
|
|
}
|
|
|
|
SourceLoc DiagnosticEngine::getBestAddImportFixItLoc(SourceFile *SF) const {
|
|
auto &SM = SourceMgr;
|
|
|
|
SourceLoc bestLoc;
|
|
if (!SF)
|
|
return bestLoc;
|
|
|
|
for (auto item : SF->getTopLevelItems()) {
|
|
// If we found an import declaration, we want to insert after it.
|
|
if (auto importDecl =
|
|
dyn_cast_or_null<ImportDecl>(item.dyn_cast<Decl *>())) {
|
|
SourceLoc loc = importDecl->getEndLoc();
|
|
if (loc.isValid()) {
|
|
bestLoc = Lexer::getLocForEndOfLine(SM, loc);
|
|
}
|
|
|
|
// Keep looking for more import declarations.
|
|
continue;
|
|
}
|
|
|
|
// If we got a location based on import declarations, we're done.
|
|
if (bestLoc.isValid())
|
|
break;
|
|
|
|
// For any other item, we want to insert before it.
|
|
SourceLoc loc = item.getStartLoc();
|
|
if (loc.isValid()) {
|
|
bestLoc = Lexer::getLocForStartOfLine(SM, loc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bestLoc;
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::fixItAddImport(StringRef ModuleName) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
auto decl = getDiag().getDecl();
|
|
if (!decl)
|
|
return *this;
|
|
|
|
auto bestLoc = Engine->getBestAddImportFixItLoc(
|
|
decl->getDeclContext()->getOutermostParentSourceFile());
|
|
if (!bestLoc.isValid())
|
|
return *this;
|
|
|
|
llvm::SmallString<64> importText;
|
|
importText += "import ";
|
|
importText += ModuleName;
|
|
importText += "\n";
|
|
|
|
return fixItInsert(bestLoc, importText);
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::fixItInsertAttribute(SourceLoc L,
|
|
const DeclAttribute *Attr) {
|
|
return fixItInsert(L, "%0 ", {Attr});
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::fixItAddAttribute(const DeclAttribute *Attr,
|
|
const ClosureExpr *E) {
|
|
ASSERT(!E->isImplicit());
|
|
|
|
SourceLoc insertionLoc = E->getBracketRange().Start;
|
|
|
|
if (insertionLoc.isInvalid()) {
|
|
if (auto *paramList = E->getParameters()) {
|
|
// HACK: Don't set insertion loc to param list start loc if it's equal to
|
|
// closure start loc (meaning it's implicit).
|
|
// FIXME: Don't set the start loc of an implicit param list, or put an
|
|
// isImplicit bit on ParameterList.
|
|
if (paramList->getStartLoc() != E->getStartLoc()) {
|
|
insertionLoc = paramList->getStartLoc();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (insertionLoc.isInvalid()) {
|
|
insertionLoc = E->getInLoc();
|
|
}
|
|
|
|
if (insertionLoc.isValid()) {
|
|
return fixItInsert(insertionLoc, "%0 ", {Attr});
|
|
} else {
|
|
auto *body = E->getBody();
|
|
|
|
insertionLoc = body->getLBraceLoc();
|
|
ASSERT(insertionLoc.isValid());
|
|
|
|
StringRef fixIt = " %0 in";
|
|
// If the first token in the body literally begins with the next char after
|
|
// '{', play it safe with a trailing space.
|
|
if (body->getContentStartLoc() ==
|
|
insertionLoc.getAdvancedLoc(/*ByteOffset=*/1)) {
|
|
fixIt = " %0 in ";
|
|
}
|
|
|
|
return fixItInsertAfter(insertionLoc, fixIt, {Attr});
|
|
}
|
|
}
|
|
|
|
InFlightDiagnostic &InFlightDiagnostic::fixItExchange(SourceRange R1,
|
|
SourceRange R2) {
|
|
assert(IsActive && "Cannot modify an inactive diagnostic");
|
|
|
|
auto &SM = Engine->SourceMgr;
|
|
// Convert from a token range to a CharSourceRange
|
|
auto charRange1 = toCharSourceRange(SM, R1);
|
|
auto charRange2 = toCharSourceRange(SM, R2);
|
|
// Extract source text.
|
|
auto text1 = SM.extractText(charRange1);
|
|
auto text2 = SM.extractText(charRange2);
|
|
|
|
getDiag().addFixIt(Diagnostic::FixIt(charRange1, "%0", {text2}));
|
|
getDiag().addFixIt(Diagnostic::FixIt(charRange2, "%0", {text1}));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::limitBehavior(DiagnosticBehavior limit) {
|
|
getDiag().setBehaviorLimit(limit);
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::limitBehaviorIfMorePermissive(DiagnosticBehavior limit) {
|
|
auto prev = getDiag().getBehaviorLimit();
|
|
getDiag().setBehaviorLimit(prev.merge(limit));
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::limitBehaviorUntilLanguageMode(DiagnosticBehavior limit,
|
|
LanguageMode mode) {
|
|
if (!mode.isEffectiveIn(Engine->languageVersion)) {
|
|
// If the behavior limit is a warning or less, wrap the diagnostic
|
|
// in a message that this will become an error in a later Swift
|
|
// version. We do this before limiting the behavior, because
|
|
// wrapIn will result in the behavior of the wrapping diagnostic.
|
|
if (limit >= DiagnosticBehavior::Warning) {
|
|
if (mode.isFuture()) {
|
|
wrapIn(diag::error_in_a_future_swift_lang_mode);
|
|
} else {
|
|
const auto majorVersion = mode.version().first;
|
|
wrapIn(diag::error_in_swift_lang_mode, majorVersion);
|
|
}
|
|
}
|
|
|
|
limitBehavior(limit);
|
|
}
|
|
|
|
// Record all of the diagnostics that are going to be emitted.
|
|
if (mode == LanguageMode::v6 && limit != DiagnosticBehavior::Ignore) {
|
|
if (auto stats = Engine->statsReporter) {
|
|
++stats->getFrontendCounters().NumSwift6Errors;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::warnUntilLanguageMode(LanguageMode mode) {
|
|
return limitBehaviorUntilLanguageMode(DiagnosticBehavior::Warning, mode);
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::warnInSwiftInterface(const DeclContext *context) {
|
|
if (context->isInSwiftinterface()) {
|
|
return limitBehavior(DiagnosticBehavior::Warning);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
InFlightDiagnostic &
|
|
InFlightDiagnostic::wrapIn(const Diagnostic &wrapper) {
|
|
// Save current active diagnostic into WrappedDiagnostics, ignoring state
|
|
// so we don't get a None return or influence future diagnostics.
|
|
DiagnosticState tempState;
|
|
Engine->state.swap(tempState);
|
|
|
|
auto &ActiveDiag = getActiveDiag();
|
|
auto &Diag = ActiveDiag.Diag;
|
|
|
|
llvm::SaveAndRestore<DiagnosticBehavior> limit(
|
|
Diag.BehaviorLimit, DiagnosticBehavior::Unspecified);
|
|
|
|
ActiveDiag.WrappedDiagnostics.push_back(std::make_unique<DiagnosticInfo>(
|
|
Engine->diagnosticInfoForDiagnostic(Diag, /*diagName*/ false).value()));
|
|
|
|
Engine->state.swap(tempState);
|
|
|
|
auto &wrapped = *ActiveDiag.WrappedDiagnostics.back();
|
|
|
|
// Copy and update its arg list.
|
|
ActiveDiag.WrappedDiagnosticArgs.emplace_back(wrapped.FormatArgs);
|
|
wrapped.FormatArgs = ActiveDiag.WrappedDiagnosticArgs.back();
|
|
|
|
// Overwrite the ID and arguments with those from the wrapper.
|
|
Diag.ID = wrapper.ID;
|
|
Diag.Args = wrapper.Args;
|
|
// Intentionally keeping the original GroupID here
|
|
|
|
// Set the argument to the diagnostic being wrapped.
|
|
ASSERT(wrapper.getArgs().front().getKind() ==
|
|
DiagnosticArgumentKind::Diagnostic);
|
|
Diag.Args.front() = &wrapped;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void InFlightDiagnostic::flush() {
|
|
if (!IsActive)
|
|
return;
|
|
|
|
IsActive = false;
|
|
if (Engine)
|
|
Engine->endDiagnostic(*this);
|
|
}
|
|
|
|
void Diagnostic::addChildNote(Diagnostic &&D) {
|
|
assert(storedDiagnosticInfos[(unsigned)D.ID].kind == DiagnosticKind::Note &&
|
|
"Only notes can have a parent.");
|
|
assert(storedDiagnosticInfos[(unsigned)ID].kind != DiagnosticKind::Note &&
|
|
"Notes can't have children.");
|
|
ChildNotes.push_back(std::move(D));
|
|
}
|
|
|
|
bool DiagnosticEngine::isDiagnosticPointsToFirstBadToken(DiagID ID) const {
|
|
return storedDiagnosticInfos[(unsigned) ID].pointsToFirstBadToken;
|
|
}
|
|
|
|
bool DiagnosticEngine::isAPIDigesterBreakageDiagnostic(DiagID ID) const {
|
|
return storedDiagnosticInfos[(unsigned)ID].isAPIDigesterBreakage;
|
|
}
|
|
|
|
bool DiagnosticEngine::isDeprecationDiagnostic(DiagID ID) const {
|
|
return storedDiagnosticInfos[(unsigned)ID].isDeprecation;
|
|
}
|
|
|
|
bool DiagnosticEngine::isNoUsageDiagnostic(DiagID ID) const {
|
|
return storedDiagnosticInfos[(unsigned)ID].isNoUsage;
|
|
}
|
|
|
|
bool DiagnosticEngine::finishProcessing() {
|
|
bool hadError = false;
|
|
for (auto &Consumer : Consumers) {
|
|
hadError |= Consumer->finishProcessing();
|
|
}
|
|
return hadError;
|
|
}
|
|
|
|
bool DiagnosticEngine::isDiagnosticGroupEnabled(SourceFile *sf, DiagGroupID groupID) const {
|
|
#if SWIFT_BUILD_SWIFT_SYNTAX
|
|
if (sf && sf->Kind != SourceFileKind::Interface &&
|
|
sf->getExportedSourceFile()) {
|
|
auto ruleRefArray = getWarningGroupBehaviorControlRefArray();
|
|
return swift_ASTGen_isWarningGroupEnabledInFile(
|
|
sf->getExportedSourceFile(), sf->getASTContext(),
|
|
BridgedArrayRef(ruleRefArray.data(), ruleRefArray.size()),
|
|
StringRef(getDiagGroupInfoByID(groupID).name));
|
|
}
|
|
return !state.isIgnoredDiagnosticGroupTree(groupID);
|
|
#else
|
|
// Fallback to checking only the command-line configuration
|
|
return !state.isIgnoredDiagnosticGroupTree(groupID);
|
|
#endif
|
|
}
|
|
|
|
void DiagnosticState::setWarningGroupControlRules(
|
|
const llvm::SmallVector<WarningGroupBehaviorRule, 4> &rules) {
|
|
for (const auto &rule : rules) {
|
|
if (rule.hasGroup()) {
|
|
getDiagGroupInfoByID(rule.getGroup()).traverseDepthFirst([&](auto group) {
|
|
addWarningGroupControl(group.id, rule.getBehavior());
|
|
});
|
|
} else {
|
|
// A global override for all groups of `-warnings-as-errors` or
|
|
// `-no-warnings-as-errors`.
|
|
for (const auto &groupInfo : diagnosticGroupsInfo) {
|
|
// Does not apply to warnings which are default ignored
|
|
if (warningGroupBehaviorMap.contains(groupInfo.id) &&
|
|
warningGroupBehaviorMap.find(groupInfo.id)->second.getBehavior() ==
|
|
WarningGroupBehavior::Ignored)
|
|
continue;
|
|
|
|
addWarningGroupControl(groupInfo.id, rule.getBehavior());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Skip forward to one of the given delimiters.
|
|
///
|
|
/// \param Text The text to search through, which will be updated to point
|
|
/// just after the delimiter.
|
|
///
|
|
/// \param Delim The first character delimiter to search for.
|
|
///
|
|
/// \param FoundDelim On return, true if the delimiter was found, or false
|
|
/// if the end of the string was reached.
|
|
///
|
|
/// \returns The string leading up to the delimiter, or the empty string
|
|
/// if no delimiter is found.
|
|
static StringRef
|
|
skipToDelimiter(StringRef &Text, char Delim, bool *FoundDelim = nullptr) {
|
|
unsigned Depth = 0;
|
|
if (FoundDelim)
|
|
*FoundDelim = false;
|
|
|
|
unsigned I = 0;
|
|
for (unsigned N = Text.size(); I != N; ++I) {
|
|
if (Text[I] == '{') {
|
|
++Depth;
|
|
continue;
|
|
}
|
|
if (Depth > 0) {
|
|
if (Text[I] == '}')
|
|
--Depth;
|
|
continue;
|
|
}
|
|
|
|
if (Text[I] == Delim) {
|
|
if (FoundDelim)
|
|
*FoundDelim = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(Depth == 0 && "Unbalanced {} set in diagnostic text");
|
|
StringRef Result = Text.substr(0, I);
|
|
Text = Text.substr(I + 1);
|
|
return Result;
|
|
}
|
|
|
|
/// Handle the integer 'select' modifier. This is used like this:
|
|
/// %select{foo|bar|baz}2. This means that the integer argument "%2" has a
|
|
/// value from 0-2. If the value is 0, the diagnostic prints 'foo'.
|
|
/// If the value is 1, it prints 'bar'. If it has the value 2, it prints 'baz'.
|
|
/// This is very useful for certain classes of variant diagnostics.
|
|
static void formatSelectionArgument(StringRef ModifierArguments,
|
|
ArrayRef<DiagnosticArgument> Args,
|
|
unsigned SelectedIndex,
|
|
DiagnosticFormatOptions FormatOpts,
|
|
llvm::raw_ostream &Out) {
|
|
bool foundPipe = false;
|
|
do {
|
|
assert((!ModifierArguments.empty() || foundPipe) &&
|
|
"Index beyond bounds in %select modifier");
|
|
StringRef Text = skipToDelimiter(ModifierArguments, '|', &foundPipe);
|
|
if (SelectedIndex == 0) {
|
|
DiagnosticEngine::formatDiagnosticText(Out, Text, Args, FormatOpts);
|
|
break;
|
|
}
|
|
--SelectedIndex;
|
|
} while (true);
|
|
|
|
}
|
|
|
|
static bool isInterestingTypealias(Type type) {
|
|
// Dig out the typealias declaration, if there is one.
|
|
TypeAliasDecl *aliasDecl = nullptr;
|
|
if (auto aliasTy = dyn_cast<TypeAliasType>(type.getPointer()))
|
|
aliasDecl = aliasTy->getDecl();
|
|
else
|
|
return false;
|
|
|
|
if (type->isVoid())
|
|
return false;
|
|
|
|
// The 'Swift.AnyObject' typealias is not 'interesting'.
|
|
if (aliasDecl->getName() ==
|
|
aliasDecl->getASTContext().getIdentifier("AnyObject") &&
|
|
(aliasDecl->getParentModule()->isStdlibModule() ||
|
|
aliasDecl->getParentModule()->isBuiltinModule())) {
|
|
return false;
|
|
}
|
|
|
|
// Compatibility aliases are only interesting insofar as their underlying
|
|
// types are interesting.
|
|
if (aliasDecl->isCompatibilityAlias()) {
|
|
auto underlyingTy = aliasDecl->getUnderlyingType();
|
|
return isInterestingTypealias(underlyingTy);
|
|
}
|
|
|
|
// Builtin types are never interesting typealiases.
|
|
if (type->is<BuiltinType>()) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Walks the type recursively desugaring types to display, but skipping
|
|
/// `GenericTypeParamType` because we would lose association with its original
|
|
/// declaration and end up presenting the parameter in τ_0_0 format on
|
|
/// diagnostic.
|
|
static Type getAkaTypeForDisplay(Type type) {
|
|
return type.transformRec([&](TypeBase *visitTy) -> std::optional<Type> {
|
|
if (isa<SugarType>(visitTy) &&
|
|
!isa<GenericTypeParamType>(visitTy))
|
|
return getAkaTypeForDisplay(visitTy->getDesugaredType());
|
|
return std::nullopt;
|
|
});
|
|
}
|
|
|
|
/// Decide whether to show the desugared type or not. We filter out some
|
|
/// cases to avoid too much noise.
|
|
static bool shouldShowAKA(Type type, StringRef typeName) {
|
|
// Canonical types are already desugared.
|
|
if (type->isCanonical())
|
|
return false;
|
|
|
|
// Only show 'aka' if there's a typealias involved; other kinds of sugar
|
|
// are easy enough for people to read on their own.
|
|
if (!type.findIf(isInterestingTypealias))
|
|
return false;
|
|
|
|
// If they are textually the same, don't show them. This can happen when
|
|
// they are actually different types, because they exist in different scopes
|
|
// (e.g. everyone names their type parameters 'T').
|
|
if (typeName == getAkaTypeForDisplay(type).getString())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// If a type is part of an argument list which includes another, distinct type
|
|
/// with the same string representation, it should be qualified during
|
|
/// formatting.
|
|
static bool typeSpellingIsAmbiguous(Type type,
|
|
ArrayRef<DiagnosticArgument> Args,
|
|
PrintOptions &PO) {
|
|
for (auto arg : Args) {
|
|
if (arg.getKind() == DiagnosticArgumentKind::Type) {
|
|
auto argType = arg.getAsType();
|
|
if (argType && argType.getPointer() != type.getPointer() &&
|
|
argType.getString(PO) == type.getString(PO)) {
|
|
// Currently, existential types are spelled the same way
|
|
// as protocols and compositions. We can remove this once
|
|
// existenials are printed with 'any'.
|
|
if (type->is<ExistentialType>() || argType->isExistentialType()) {
|
|
auto constraint = type;
|
|
if (auto existential = type->getAs<ExistentialType>())
|
|
constraint = existential->getConstraintType();
|
|
|
|
auto argConstraint = argType;
|
|
if (auto existential = argType->getAs<ExistentialType>())
|
|
argConstraint = existential->getConstraintType();
|
|
|
|
if (constraint.getPointer() != argConstraint.getPointer())
|
|
return true;
|
|
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void swift::printClangDeclName(const clang::NamedDecl *ND,
|
|
llvm::raw_ostream &os) {
|
|
ND->getNameForDiagnostic(os, ND->getASTContext().getPrintingPolicy(), false);
|
|
}
|
|
|
|
void swift::printClangTypeName(const clang::Type *Ty, llvm::raw_ostream &os) {
|
|
clang::QualType::print(Ty, clang::Qualifiers(), os,
|
|
clang::PrintingPolicy{clang::LangOptions()}, "");
|
|
}
|
|
|
|
/// Format a single diagnostic argument and write it to the given
|
|
/// stream.
|
|
static void formatDiagnosticArgument(StringRef Modifier,
|
|
StringRef ModifierArguments,
|
|
ArrayRef<DiagnosticArgument> Args,
|
|
unsigned ArgIndex,
|
|
DiagnosticFormatOptions FormatOpts,
|
|
llvm::raw_ostream &Out) {
|
|
const DiagnosticArgument &Arg = Args[ArgIndex];
|
|
switch (Arg.getKind()) {
|
|
case DiagnosticArgumentKind::Integer:
|
|
if (Modifier == "select") {
|
|
assert(Arg.getAsInteger() >= 0 && "Negative selection index");
|
|
formatSelectionArgument(ModifierArguments, Args, Arg.getAsInteger(),
|
|
FormatOpts, Out);
|
|
} else if (Modifier == "s") {
|
|
if (Arg.getAsInteger() != 1)
|
|
Out << 's';
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for integer argument");
|
|
Out << Arg.getAsInteger();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::Unsigned:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args, Arg.getAsUnsigned(),
|
|
FormatOpts, Out);
|
|
} else if (Modifier == "s") {
|
|
if (Arg.getAsUnsigned() != 1)
|
|
Out << 's';
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for unsigned argument");
|
|
Out << Arg.getAsUnsigned();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::String:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args,
|
|
Arg.getAsString().empty() ? 0 : 1, FormatOpts,
|
|
Out);
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for string argument");
|
|
Out << Arg.getAsString();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::Identifier:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args,
|
|
Arg.getAsIdentifier() ? 1 : 0, FormatOpts,
|
|
Out);
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for identifier argument");
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
Arg.getAsIdentifier().printPretty(Out);
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::ObjCSelector:
|
|
assert(Modifier.empty() && "Improper modifier for selector argument");
|
|
Out << FormatOpts.OpeningQuotationMark << Arg.getAsObjCSelector()
|
|
<< FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::Decl: {
|
|
auto D = Arg.getAsDecl();
|
|
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args, D ? 1 : 0, FormatOpts,
|
|
Out);
|
|
break;
|
|
}
|
|
|
|
// Parse info out of modifier
|
|
bool includeKind = false;
|
|
bool includeName = true;
|
|
bool baseNameOnly = false;
|
|
|
|
if (Modifier == "kind") {
|
|
includeKind = true;
|
|
} else if (Modifier == "base") {
|
|
baseNameOnly = true;
|
|
} else if (Modifier == "kindbase") {
|
|
includeKind = true;
|
|
baseNameOnly = true;
|
|
} else if (Modifier == "kindonly") {
|
|
includeName = false;
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for ValueDecl argument");
|
|
}
|
|
|
|
// Handle declarations representing an AvailabilityDomain specially.
|
|
if (auto VD = dyn_cast<ValueDecl>(D)) {
|
|
if (auto domain = AvailabilityDomain::forCustom(const_cast<ValueDecl *>(VD))) {
|
|
Out << "availability domain";
|
|
|
|
if (includeName) {
|
|
Out << " " << FormatOpts.OpeningQuotationMark;
|
|
Out << domain->getNameForDiagnostics();
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (includeName) {
|
|
if (auto accessor = dyn_cast<AccessorDecl>(D)) {
|
|
// If it's an accessor, describe that and then switch to discussing its
|
|
// storage declaration.
|
|
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind()) << " for ";
|
|
D = accessor->getStorage();
|
|
} else if (auto ext = dyn_cast<ExtensionDecl>(D)) {
|
|
// If it's an extension with a valid bound type declaration, describe
|
|
// the extension itself then switch to discussing the bound
|
|
// declaration.
|
|
if (auto *nominal = ext->getSelfNominalTypeDecl()) {
|
|
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind())
|
|
<< " of ";
|
|
D = nominal;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Figure out the name we want to print.
|
|
DeclName name;
|
|
if (includeName) {
|
|
if (auto MD = dyn_cast<ModuleDecl>(D))
|
|
name = MD->getPublicModuleName(/*onlyIfImported=*/true);
|
|
else if (auto VD = dyn_cast<ValueDecl>(D))
|
|
name = VD->getName();
|
|
else if (auto PGD = dyn_cast<PrecedenceGroupDecl>(D))
|
|
name = PGD->getName();
|
|
else if (auto OD = dyn_cast<OperatorDecl>(D))
|
|
name = OD->getName();
|
|
else if (auto MMD = dyn_cast<MissingMemberDecl>(D))
|
|
name = MMD->getName();
|
|
|
|
if (baseNameOnly && name)
|
|
name = name.getBaseName();
|
|
}
|
|
|
|
// If the declaration is anonymous or we asked for a descriptive kind, print
|
|
// it.
|
|
if (!name || includeKind) {
|
|
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind());
|
|
if (name)
|
|
Out << " ";
|
|
}
|
|
|
|
// Print the name.
|
|
if (name) {
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
name.printPretty(Out);
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DiagnosticArgumentKind::FullyQualifiedType:
|
|
case DiagnosticArgumentKind::Type:
|
|
case DiagnosticArgumentKind::WitnessType: {
|
|
std::optional<DiagnosticFormatOptions> TypeFormatOpts;
|
|
if (Modifier == "noformat") {
|
|
TypeFormatOpts.emplace(DiagnosticFormatOptions::formatForFixIts());
|
|
} else {
|
|
assert(Modifier.empty() && "Improper modifier for Type argument");
|
|
TypeFormatOpts.emplace(FormatOpts);
|
|
}
|
|
|
|
// Strip extraneous parentheses; they add no value.
|
|
Type type;
|
|
bool needsQualification = false;
|
|
|
|
// Compute the appropriate print options for this argument.
|
|
auto printOptions = PrintOptions::forDiagnosticArguments();
|
|
if (Arg.getKind() == DiagnosticArgumentKind::Type) {
|
|
type = Arg.getAsType();
|
|
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
|
|
printOptions.PrintFunctionRepresentationAttrs =
|
|
PrintOptions::FunctionRepresentationMode::Full;
|
|
needsQualification = typeSpellingIsAmbiguous(type, Args, printOptions);
|
|
} else if (Arg.getKind() == DiagnosticArgumentKind::FullyQualifiedType) {
|
|
type = Arg.getAsFullyQualifiedType().getType();
|
|
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
|
|
printOptions.PrintFunctionRepresentationAttrs =
|
|
PrintOptions::FunctionRepresentationMode::Full;
|
|
needsQualification = true;
|
|
} else {
|
|
assert(Arg.getKind() == DiagnosticArgumentKind::WitnessType);
|
|
type = Arg.getAsWitnessType().getType();
|
|
printOptions.PrintGenericRequirements = false;
|
|
printOptions.PrintInverseRequirements = false;
|
|
needsQualification = typeSpellingIsAmbiguous(type, Args, printOptions);
|
|
}
|
|
|
|
// If a type has a bare error type, print it with syntax sugar removed for
|
|
// clarity. For example, print `Array<_>` instead of `[_]`.
|
|
if (type->hasBareError())
|
|
type = type->getWithoutSyntaxSugar();
|
|
|
|
if (needsQualification &&
|
|
isa<OpaqueTypeArchetypeType>(type.getPointer()) &&
|
|
cast<ArchetypeType>(type.getPointer())->isRoot()) {
|
|
auto opaqueTypeDecl = type->castTo<OpaqueTypeArchetypeType>()->getDecl();
|
|
|
|
llvm::SmallString<256> NamingDeclText;
|
|
llvm::raw_svector_ostream OutNaming(NamingDeclText);
|
|
auto namingDecl = opaqueTypeDecl->getNamingDecl();
|
|
if (namingDecl->getDeclContext()->isTypeContext()) {
|
|
auto selfTy = namingDecl->getDeclContext()->getSelfInterfaceType();
|
|
selfTy->print(OutNaming);
|
|
OutNaming << '.';
|
|
}
|
|
namingDecl->getName().printPretty(OutNaming);
|
|
|
|
auto descriptiveKind = opaqueTypeDecl->getDescriptiveKind();
|
|
|
|
Out << llvm::format(TypeFormatOpts->OpaqueResultFormatString.c_str(),
|
|
type->getString(printOptions).c_str(),
|
|
Decl::getDescriptiveKindName(descriptiveKind).data(),
|
|
NamingDeclText.c_str());
|
|
|
|
} else {
|
|
printOptions.FullyQualifiedTypes = needsQualification;
|
|
std::string typeName = type->getString(printOptions);
|
|
|
|
if (shouldShowAKA(type, typeName)) {
|
|
llvm::SmallString<256> AkaText;
|
|
llvm::raw_svector_ostream OutAka(AkaText);
|
|
|
|
getAkaTypeForDisplay(type)->print(OutAka, printOptions);
|
|
Out << llvm::format(TypeFormatOpts->AKAFormatString.c_str(),
|
|
typeName.c_str(), AkaText.c_str());
|
|
} else {
|
|
Out << TypeFormatOpts->OpeningQuotationMark << typeName
|
|
<< TypeFormatOpts->ClosingQuotationMark;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DiagnosticArgumentKind::TypeRepr:
|
|
assert(Modifier.empty() && "Improper modifier for TypeRepr argument");
|
|
assert(Arg.getAsTypeRepr() && "TypeRepr argument is null");
|
|
Out << FormatOpts.OpeningQuotationMark << Arg.getAsTypeRepr()
|
|
<< FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::DescriptivePatternKind:
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for DescriptivePatternKind argument");
|
|
Out << Pattern::getDescriptivePatternKindName(
|
|
Arg.getAsDescriptivePatternKind());
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::SelfAccessKind:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args,
|
|
unsigned(Arg.getAsSelfAccessKind()),
|
|
FormatOpts, Out);
|
|
} else {
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for SelfAccessKind argument");
|
|
Out << Arg.getAsSelfAccessKind();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::ReferenceOwnership:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args,
|
|
unsigned(Arg.getAsReferenceOwnership()),
|
|
FormatOpts, Out);
|
|
} else {
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for ReferenceOwnership argument");
|
|
Out << Arg.getAsReferenceOwnership();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::StaticSpellingKind:
|
|
if (Modifier == "select") {
|
|
formatSelectionArgument(ModifierArguments, Args,
|
|
unsigned(Arg.getAsStaticSpellingKind()),
|
|
FormatOpts, Out);
|
|
} else {
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for StaticSpellingKind argument");
|
|
Out << Arg.getAsStaticSpellingKind();
|
|
}
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::DescriptiveDeclKind:
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for DescriptiveDeclKind argument");
|
|
Out << Decl::getDescriptiveKindName(Arg.getAsDescriptiveDeclKind());
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::DescriptiveStmtKind:
|
|
assert(Modifier.empty() && "Improper modifier for StmtKind argument");
|
|
Out << Stmt::getDescriptiveKindName(Arg.getAsDescriptiveStmtKind());
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::DeclAttribute: {
|
|
auto *const attr = Arg.getAsDeclAttribute();
|
|
const auto printAttrName = [&] {
|
|
if (auto *custom = dyn_cast<CustomAttr>(attr)) {
|
|
custom->getTypeRepr()->print(Out);
|
|
} else {
|
|
Out << attr->getAttrName();
|
|
}
|
|
};
|
|
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for DeclAttribute argument");
|
|
if (Arg.getAsDeclAttribute()->isDeclModifier()) {
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
printAttrName();
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
} else {
|
|
Out << '@';
|
|
printAttrName();
|
|
}
|
|
break;
|
|
}
|
|
case DiagnosticArgumentKind::TypeAttribute: {
|
|
bool useAtStyle = true;
|
|
if (Modifier == "kind") {
|
|
useAtStyle = false;
|
|
} else {
|
|
ASSERT(Modifier.empty() &&
|
|
"Improper modifier for TypeAttribute argument");
|
|
}
|
|
|
|
if (!useAtStyle) {
|
|
Out << "attribute ";
|
|
}
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
if (useAtStyle) {
|
|
Out << '@';
|
|
}
|
|
Out << Arg.getAsTypeAttribute()->getAttrName();
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
}
|
|
case DiagnosticArgumentKind::AvailabilityDomain:
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for AvailabilityDomain argument");
|
|
Out << Arg.getAsAvailabilityDomain().getNameForDiagnostics();
|
|
break;
|
|
case DiagnosticArgumentKind::AvailabilityRange:
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for AvailabilityRange argument");
|
|
Out << Arg.getAsAvailabilityRange().getRawMinimumVersion().getAsString();
|
|
break;
|
|
case DiagnosticArgumentKind::VersionTuple:
|
|
assert(Modifier.empty() &&
|
|
"Improper modifier for VersionTuple argument");
|
|
Out << Arg.getAsVersionTuple().getAsString();
|
|
break;
|
|
case DiagnosticArgumentKind::LayoutConstraint:
|
|
assert(Modifier.empty() && "Improper modifier for LayoutConstraint argument");
|
|
Out << FormatOpts.OpeningQuotationMark << Arg.getAsLayoutConstraint()
|
|
<< FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
case DiagnosticArgumentKind::ActorIsolation: {
|
|
assert((Modifier.empty() || Modifier == "noun") &&
|
|
"Improper modifier for ActorIsolation argument");
|
|
auto isolation = Arg.getAsActorIsolation();
|
|
isolation.printForDiagnostics(Out, FormatOpts.OpeningQuotationMark,
|
|
/*asNoun*/ Modifier == "noun");
|
|
break;
|
|
}
|
|
case DiagnosticArgumentKind::IsolationSource: {
|
|
assert(Modifier.empty() && "Improper modifier for IsolationSource argument");
|
|
auto isolation = Arg.getAsIsolationSource();
|
|
isolation.printForDiagnostics(Out, FormatOpts.OpeningQuotationMark);
|
|
break;
|
|
}
|
|
case DiagnosticArgumentKind::Diagnostic: {
|
|
assert(Modifier.empty() && "Improper modifier for Diagnostic argument");
|
|
auto diagArg = Arg.getAsDiagnostic();
|
|
DiagnosticEngine::formatDiagnosticText(Out, diagArg->FormatString,
|
|
diagArg->FormatArgs);
|
|
break;
|
|
}
|
|
|
|
case DiagnosticArgumentKind::ClangDecl:
|
|
assert(Modifier.empty() && "Improper modifier for ClangDecl argument");
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
printClangDeclName(Arg.getAsClangDecl(), Out);
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
|
|
case DiagnosticArgumentKind::ClangType:
|
|
assert(Modifier.empty() && "Improper modifier for ClangDecl argument");
|
|
Out << FormatOpts.OpeningQuotationMark;
|
|
printClangTypeName(Arg.getAsClangType(), Out);
|
|
Out << FormatOpts.ClosingQuotationMark;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Format the given diagnostic text and place the result in the given
|
|
/// buffer.
|
|
void DiagnosticEngine::formatDiagnosticText(
|
|
llvm::raw_ostream &Out, StringRef InText, ArrayRef<DiagnosticArgument> Args,
|
|
DiagnosticFormatOptions FormatOpts) {
|
|
while (!InText.empty()) {
|
|
size_t Percent = InText.find('%');
|
|
if (Percent == StringRef::npos) {
|
|
// Write the rest of the string; we're done.
|
|
Out.write(InText.data(), InText.size());
|
|
break;
|
|
}
|
|
|
|
// Write the string up to (but not including) the %, then drop that text
|
|
// (including the %).
|
|
Out.write(InText.data(), Percent);
|
|
InText = InText.substr(Percent + 1);
|
|
|
|
// '%%' -> '%'.
|
|
if (InText[0] == '%') {
|
|
Out.write('%');
|
|
InText = InText.substr(1);
|
|
continue;
|
|
}
|
|
|
|
// Parse an optional modifier.
|
|
StringRef Modifier;
|
|
{
|
|
size_t Length = InText.find_if_not(isalpha);
|
|
Modifier = InText.substr(0, Length);
|
|
InText = InText.substr(Length);
|
|
}
|
|
|
|
if (Modifier == "error") {
|
|
Out << StringRef("<<INTERNAL ERROR: encountered %error in diagnostic text>>");
|
|
continue;
|
|
}
|
|
|
|
// Parse the optional argument list for a modifier, which is brace-enclosed.
|
|
StringRef ModifierArguments;
|
|
if (InText[0] == '{') {
|
|
InText = InText.substr(1);
|
|
ModifierArguments = skipToDelimiter(InText, '}');
|
|
}
|
|
|
|
// Find the digit sequence, and parse it into an argument index.
|
|
size_t Length = InText.find_if_not(isdigit);
|
|
unsigned ArgIndex;
|
|
bool IndexParseFailed = InText.substr(0, Length).getAsInteger(10, ArgIndex);
|
|
|
|
if (IndexParseFailed) {
|
|
Out << StringRef("<<INTERNAL ERROR: unparseable argument index in diagnostic text>>");
|
|
continue;
|
|
}
|
|
|
|
InText = InText.substr(Length);
|
|
|
|
if (ArgIndex >= Args.size()) {
|
|
Out << StringRef("<<INTERNAL ERROR: out-of-range argument index in diagnostic text>>");
|
|
continue;
|
|
}
|
|
|
|
// Convert the argument to a string.
|
|
formatDiagnosticArgument(Modifier, ModifierArguments, Args, ArgIndex,
|
|
FormatOpts, Out);
|
|
}
|
|
}
|
|
|
|
static DiagnosticKind toDiagnosticKind(DiagnosticBehavior behavior) {
|
|
switch (behavior) {
|
|
case DiagnosticBehavior::Unspecified:
|
|
llvm_unreachable("unspecified behavior");
|
|
case DiagnosticBehavior::Ignore:
|
|
llvm_unreachable("trying to map an ignored diagnostic");
|
|
case DiagnosticBehavior::Error:
|
|
case DiagnosticBehavior::Fatal:
|
|
return DiagnosticKind::Error;
|
|
case DiagnosticBehavior::Note:
|
|
return DiagnosticKind::Note;
|
|
case DiagnosticBehavior::Warning:
|
|
return DiagnosticKind::Warning;
|
|
case DiagnosticBehavior::Remark:
|
|
return DiagnosticKind::Remark;
|
|
}
|
|
|
|
llvm_unreachable("Unhandled DiagnosticKind in switch.");
|
|
}
|
|
|
|
static
|
|
DiagnosticBehavior toDiagnosticBehavior(DiagnosticKind kind, bool isFatal) {
|
|
switch (kind) {
|
|
case DiagnosticKind::Note:
|
|
return DiagnosticBehavior::Note;
|
|
case DiagnosticKind::Error:
|
|
return isFatal ? DiagnosticBehavior::Fatal : DiagnosticBehavior::Error;
|
|
case DiagnosticKind::Warning:
|
|
return DiagnosticBehavior::Warning;
|
|
case DiagnosticKind::Remark:
|
|
return DiagnosticBehavior::Remark;
|
|
}
|
|
llvm_unreachable("Unhandled DiagnosticKind in switch.");
|
|
}
|
|
|
|
std::optional<DiagnosticBehavior>
|
|
DiagnosticState::determineUserControlledWarningBehavior(
|
|
const Diagnostic &diag, SourceManager &sourceMgr) const {
|
|
auto &diagInfo = storedDiagnosticInfos[(unsigned)diag.getID()];
|
|
if (diagInfo.kind != DiagnosticKind::Warning)
|
|
return std::nullopt;
|
|
|
|
// Compute a behavior relying strictly on command-line provided
|
|
// `warningGroupBehaviorMap`.
|
|
std::optional<DiagnosticBehavior> userControlledBehavior;
|
|
if (warningGroupBehaviorMap.contains(diag.getGroupID())) {
|
|
switch (
|
|
warningGroupBehaviorMap.find(diag.getGroupID())->second.getBehavior()) {
|
|
case WarningGroupBehavior::AsWarning:
|
|
userControlledBehavior = DiagnosticBehavior::Warning;
|
|
break;
|
|
case WarningGroupBehavior::AsError:
|
|
userControlledBehavior = DiagnosticBehavior::Error;
|
|
break;
|
|
case WarningGroupBehavior::Ignored:
|
|
userControlledBehavior = DiagnosticBehavior::Ignore;
|
|
break;
|
|
case WarningGroupBehavior::None:
|
|
userControlledBehavior = std::nullopt;
|
|
break;
|
|
}
|
|
} else
|
|
userControlledBehavior = std::nullopt;
|
|
|
|
#if SWIFT_BUILD_SWIFT_SYNTAX
|
|
// Use the combined global controls (command-line flags such as `-Werror`)
|
|
// and syntactic controls at the source location of this diagnostic to
|
|
// compute the configured emission behavior for this diagnostic group.
|
|
SourceLoc loc = diag.getLocOrDeclLoc();
|
|
if (loc.isValid()) {
|
|
auto sourceFiles = sourceMgr.getSourceFilesForBufferID(
|
|
sourceMgr.findBufferContainingLoc(loc));
|
|
if (!sourceFiles.empty()) {
|
|
SourceFile *SF = sourceFiles.front();
|
|
// Don't run syntactic @diagnose controls for .swiftinterface files.
|
|
if (SF && SF->Kind != SourceFileKind::Interface &&
|
|
SF->getExportedSourceFile()) {
|
|
auto ruleRefArray = getWarningGroupBehaviorControlRefArray();
|
|
WarningGroupBehavior behavior =
|
|
swift_ASTGen_warningGroupBehaviorAtPosition(
|
|
SF->getExportedSourceFile(), SF->getASTContext(),
|
|
BridgedArrayRef(ruleRefArray.data(), ruleRefArray.size()),
|
|
StringRef(getDiagGroupInfoByID(diag.getGroupID()).name), loc);
|
|
switch (behavior) {
|
|
case AsError:
|
|
userControlledBehavior = DiagnosticBehavior::Error;
|
|
break;
|
|
case AsWarning:
|
|
userControlledBehavior = DiagnosticBehavior::Warning;
|
|
break;
|
|
case Ignored:
|
|
userControlledBehavior = DiagnosticBehavior::Ignore;
|
|
break;
|
|
case None:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return userControlledBehavior;
|
|
}
|
|
|
|
DiagnosticBehavior
|
|
DiagnosticState::determineBehavior(const Diagnostic &diag,
|
|
SourceManager &sourceMgr) const {
|
|
// We determine how to handle a diagnostic based on the following rules
|
|
// 1) Map the diagnostic to its "intended" behavior, applying the behavior
|
|
// limit for this particular emission
|
|
// 2) If current state dictates a certain behavior, follow that
|
|
// 3) If the user ignored this specific diagnostic, follow that
|
|
// 4) If the user substituted a different behavior for this behavior, apply
|
|
// that change
|
|
// 5) Update current state for use during the next diagnostic
|
|
|
|
// 1) Map the diagnostic to its "intended" behavior, applying the behavior
|
|
// limit for this particular emission
|
|
auto diagInfo = storedDiagnosticInfos[(unsigned)diag.getID()];
|
|
DiagnosticBehavior lvl =
|
|
std::max(toDiagnosticBehavior(diagInfo.kind, diagInfo.isFatal),
|
|
diag.getBehaviorLimit());
|
|
assert(lvl != DiagnosticBehavior::Unspecified);
|
|
|
|
// 2) If current state dictates a certain behavior, follow that
|
|
|
|
// Notes relating to ignored diagnostics should also be ignored
|
|
if (previousBehavior == DiagnosticBehavior::Ignore
|
|
&& lvl == DiagnosticBehavior::Note)
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
|
|
// Suppress diagnostics when in a fatal state, except for follow-on notes
|
|
if (fatalErrorOccurred)
|
|
if (!showDiagnosticsAfterFatalError && lvl != DiagnosticBehavior::Note)
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
|
|
// Handle compiler-internal ignored diagnostics
|
|
if (compilerIgnoredDiagnostics[(unsigned)diag.getID()])
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
|
|
// 3) If the user substituted a different behavior for this warning, apply
|
|
// that change
|
|
if (lvl == DiagnosticBehavior::Warning) {
|
|
if (auto userControlBehavior =
|
|
determineUserControlledWarningBehavior(diag, sourceMgr))
|
|
lvl = *userControlBehavior;
|
|
if (suppressWarnings)
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
}
|
|
|
|
if (lvl == DiagnosticBehavior::Note) {
|
|
if (suppressNotes)
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
}
|
|
|
|
if (lvl == DiagnosticBehavior::Remark) {
|
|
if (suppressRemarks)
|
|
lvl = DiagnosticBehavior::Ignore;
|
|
}
|
|
return lvl;
|
|
}
|
|
|
|
void DiagnosticState::updateFor(DiagnosticBehavior behavior) {
|
|
// Update current state for use during the next diagnostic
|
|
if (behavior == DiagnosticBehavior::Fatal) {
|
|
fatalErrorOccurred = true;
|
|
anyErrorOccurred = true;
|
|
} else if (behavior == DiagnosticBehavior::Error) {
|
|
anyErrorOccurred = true;
|
|
}
|
|
|
|
ASSERT((!assertOnError || !anyErrorOccurred) && "We emitted an error?!");
|
|
ASSERT((!assertOnWarning || (behavior != DiagnosticBehavior::Warning)) &&
|
|
"We emitted a warning?!");
|
|
|
|
previousBehavior = behavior;
|
|
}
|
|
|
|
InFlightDiagnostic DiagnosticEngine::beginDiagnostic(const Diagnostic &D) {
|
|
unsigned idx = ActiveDiagnostics.size();
|
|
ActiveDiagnostics.emplace_back(D);
|
|
NumActiveDiags += 1;
|
|
return InFlightDiagnostic(*this, idx);
|
|
}
|
|
|
|
void DiagnosticEngine::endDiagnostic(const InFlightDiagnostic &D) {
|
|
// Decrement the number of active diagnostics. We wait until all in-flight
|
|
// diagnostics have ended to ensure a FIFO ordering.
|
|
ASSERT(NumActiveDiags > 0 && "Unbalanced call to endDiagnostic");
|
|
NumActiveDiags -= 1;
|
|
if (NumActiveDiags > 0)
|
|
return;
|
|
|
|
for (auto &D : ActiveDiagnostics)
|
|
handleDiagnostic(std::move(D));
|
|
|
|
ActiveDiagnostics.clear();
|
|
}
|
|
|
|
detail::ActiveDiagnostic &
|
|
DiagnosticEngine::getActiveDiagnostic(const InFlightDiagnostic &diag) {
|
|
return ActiveDiagnostics[diag.Idx];
|
|
}
|
|
|
|
void DiagnosticEngine::handleDiagnostic(detail::ActiveDiagnostic &&diag) {
|
|
if (TransactionCount == 0) {
|
|
emitDiagnostic(diag.Diag);
|
|
} else {
|
|
onTentativeDiagnosticFlush(diag.Diag);
|
|
TentativeDiagnostics.emplace_back(std::move(diag));
|
|
}
|
|
}
|
|
|
|
void DiagnosticEngine::clearTentativeDiagnostics() {
|
|
TentativeDiagnostics.clear();
|
|
}
|
|
|
|
void DiagnosticEngine::emitTentativeDiagnostics() {
|
|
for (auto &diag : TentativeDiagnostics) {
|
|
emitDiagnostic(diag.Diag);
|
|
}
|
|
clearTentativeDiagnostics();
|
|
}
|
|
|
|
void DiagnosticEngine::forwardTentativeDiagnosticsTo(
|
|
DiagnosticEngine &targetEngine) {
|
|
for (auto &diag : TentativeDiagnostics) {
|
|
targetEngine.handleDiagnostic(std::move(diag));
|
|
}
|
|
clearTentativeDiagnostics();
|
|
}
|
|
|
|
std::optional<DiagnosticInfo>
|
|
DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic,
|
|
bool includeDiagnosticName) {
|
|
auto behavior = state.determineBehavior(diagnostic, SourceMgr);
|
|
state.updateFor(behavior);
|
|
|
|
if (behavior != DiagnosticBehavior::Ignore) {
|
|
auto groupID = diagnostic.getGroupID();
|
|
if (groupID != DiagGroupID::no_group &&
|
|
state.shouldAssertOnGroup(groupID)) {
|
|
ASSERT(false && "Trapping on diagnostic group (see "
|
|
"-diagnostics-assert-on-group)");
|
|
}
|
|
}
|
|
|
|
if (behavior == DiagnosticBehavior::Ignore)
|
|
return std::nullopt;
|
|
|
|
// Figure out the source location.
|
|
SourceLoc loc = diagnostic.getLocOrDeclLoc();
|
|
if (loc.isInvalid() && diagnostic.getDecl()) {
|
|
// If the location of the decl is invalid, try to pretty-print it into a
|
|
// buffer and capture the source location there. Make sure we don't have an
|
|
// active request running since printing AST can kick requests that may
|
|
// themselves emit diagnostics. This won't help the underlying cycle, but it
|
|
// at least stops us from overflowing the stack.
|
|
const Decl *decl = diagnostic.getDecl();
|
|
PrettyPrintDeclRequest req(decl);
|
|
auto &eval = decl->getASTContext().evaluator;
|
|
if (!eval.hasActiveRequest(req))
|
|
loc = evaluateOrDefault(eval, req, SourceLoc());
|
|
}
|
|
|
|
auto groupID = diagnostic.getGroupID();
|
|
StringRef CategoryName;
|
|
if (auto wrapped = diagnostic.getWrappedDiagnostic())
|
|
CategoryName = wrapped.value()->getCategoryName();
|
|
else if (groupID != DiagGroupID::no_group)
|
|
CategoryName = getDiagGroupInfoByID(groupID).name;
|
|
else if (isAPIDigesterBreakageDiagnostic(diagnostic.getID()))
|
|
CategoryName = "api-digester-breaking-change";
|
|
else if (isNoUsageDiagnostic(diagnostic.getID()))
|
|
CategoryName = "no-usage";
|
|
else if (isDeprecationDiagnostic(diagnostic.getID()))
|
|
CategoryName = "deprecation";
|
|
|
|
auto fixIts = diagnostic.getFixIts();
|
|
if (loc.isValid()) {
|
|
// If the diagnostic is being emitted in a generated buffer, drop the
|
|
// fix-its, as the user will have no way of applying them.
|
|
auto bufferID = SourceMgr.findBufferContainingLoc(loc);
|
|
if (auto generatedInfo = SourceMgr.getGeneratedSourceInfo(bufferID)) {
|
|
switch (generatedInfo->kind) {
|
|
#define MACRO_ROLE(Name, Description) \
|
|
case GeneratedSourceInfo::Name##MacroExpansion:
|
|
#include "swift/Basic/MacroRoles.def"
|
|
case GeneratedSourceInfo::PrettyPrinted:
|
|
case GeneratedSourceInfo::DefaultArgument:
|
|
case GeneratedSourceInfo::AttributeFromClang:
|
|
case GeneratedSourceInfo::SyntheticMacro:
|
|
fixIts = {};
|
|
break;
|
|
case GeneratedSourceInfo::ReplacedFunctionBody:
|
|
// A replaced function body is for user-written code, so fix-its are
|
|
// still valid.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto formatString =
|
|
getFormatStringForDiagnostic(diagnostic, includeDiagnosticName);
|
|
|
|
return DiagnosticInfo(diagnostic.getID(), loc, toDiagnosticKind(behavior),
|
|
formatString, diagnostic.getArgs(), CategoryName,
|
|
getDefaultDiagnosticLoc(),
|
|
/*child note info*/ {}, diagnostic.getRanges(), fixIts,
|
|
diagnostic.isChildNote());
|
|
}
|
|
|
|
static DeclName
|
|
getGeneratedSourceInfoMacroName(const GeneratedSourceInfo &info) {
|
|
ASTNode expansionNode = ASTNode::getFromOpaqueValue(info.astNode);
|
|
switch (info.kind) {
|
|
#define MACRO_ROLE(Name, Description) \
|
|
case GeneratedSourceInfo::Name##MacroExpansion:
|
|
#include "swift/Basic/MacroRoles.def"
|
|
{
|
|
if (auto customAttr = info.attachedMacroCustomAttr) {
|
|
// FIXME: How will we handle deserialized custom attributes like this?
|
|
auto declRefType = cast<DeclRefTypeRepr>(customAttr->getTypeRepr());
|
|
return declRefType->getNameRef().getFullName();
|
|
}
|
|
|
|
if (auto expansionExpr = dyn_cast_or_null<MacroExpansionExpr>(
|
|
expansionNode.dyn_cast<Expr *>())) {
|
|
return expansionExpr->getMacroName().getFullName();
|
|
}
|
|
|
|
auto expansionDecl =
|
|
cast<MacroExpansionDecl>(cast<Decl *>(expansionNode));
|
|
return expansionDecl->getMacroName().getFullName();
|
|
}
|
|
|
|
case GeneratedSourceInfo::PrettyPrinted:
|
|
case GeneratedSourceInfo::ReplacedFunctionBody:
|
|
case GeneratedSourceInfo::DefaultArgument:
|
|
case GeneratedSourceInfo::AttributeFromClang:
|
|
case GeneratedSourceInfo::SyntheticMacro:
|
|
return DeclName();
|
|
}
|
|
}
|
|
|
|
std::vector<Diagnostic>
|
|
DiagnosticEngine::getGeneratedSourceBufferNotes(SourceLoc loc) {
|
|
// The set of child notes we're building up.
|
|
std::vector<Diagnostic> childNotes;
|
|
|
|
// If the location is invalid, there's nothing to do.
|
|
if (loc.isInvalid())
|
|
return childNotes;
|
|
|
|
// If we already emitted these notes for a prior part of the diagnostic,
|
|
// don't do so again.
|
|
auto currentBufferID = SourceMgr.findBufferContainingLoc(loc);
|
|
SourceLoc currentLoc = loc;
|
|
do {
|
|
auto generatedInfo = SourceMgr.getGeneratedSourceInfo(currentBufferID);
|
|
if (!generatedInfo)
|
|
return childNotes;
|
|
|
|
ASTNode expansionNode =
|
|
ASTNode::getFromOpaqueValue(generatedInfo->astNode);
|
|
|
|
switch (generatedInfo->kind) {
|
|
#define MACRO_ROLE(Name, Description) \
|
|
case GeneratedSourceInfo::Name##MacroExpansion:
|
|
#include "swift/Basic/MacroRoles.def"
|
|
{
|
|
DeclName macroName = getGeneratedSourceInfoMacroName(*generatedInfo);
|
|
|
|
// If it was an expansion of an attached macro, increase the range to
|
|
// include the decl's attributes. Also add the name of the decl the macro
|
|
// is attached to.
|
|
CustomAttr *attachedAttr = generatedInfo->attachedMacroCustomAttr;
|
|
Decl *attachedDecl =
|
|
attachedAttr ? expansionNode.dyn_cast<Decl *>() : nullptr;
|
|
SourceRange origRange = attachedDecl
|
|
? attachedDecl->getSourceRangeIncludingAttrs()
|
|
: expansionNode.getSourceRange();
|
|
|
|
Diagnostic expansionNote(diag::in_macro_expansion, macroName,
|
|
attachedDecl);
|
|
if (attachedAttr) {
|
|
expansionNote.setLoc(attachedAttr->getLocation());
|
|
} else {
|
|
expansionNote.setLoc(origRange.Start);
|
|
}
|
|
expansionNote.addRange(
|
|
Lexer::getCharSourceRangeFromSourceRange(SourceMgr, origRange));
|
|
expansionNote.setIsChildNote(true);
|
|
childNotes.push_back(std::move(expansionNote));
|
|
break;
|
|
}
|
|
|
|
case GeneratedSourceInfo::PrettyPrinted:
|
|
break;
|
|
|
|
case GeneratedSourceInfo::DefaultArgument:
|
|
case GeneratedSourceInfo::ReplacedFunctionBody:
|
|
case GeneratedSourceInfo::AttributeFromClang:
|
|
case GeneratedSourceInfo::SyntheticMacro:
|
|
return childNotes;
|
|
}
|
|
|
|
// Walk up the stack.
|
|
currentLoc = expansionNode.getStartLoc();
|
|
if (currentLoc.isInvalid())
|
|
return childNotes;
|
|
|
|
currentBufferID = SourceMgr.findBufferContainingLoc(currentLoc);
|
|
} while (true);
|
|
}
|
|
|
|
void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
|
|
|
|
ArrayRef<Diagnostic> childNotes = diagnostic.getChildNotes();
|
|
std::vector<Diagnostic> extendedChildNotes;
|
|
|
|
if (auto info =
|
|
diagnosticInfoForDiagnostic(diagnostic,
|
|
/* includeDiagnosticName= */ true)) {
|
|
// If the diagnostic location is within a buffer containing generated
|
|
// source code, add child notes showing where the generation occurred.
|
|
// We need to avoid doing this if this is itself a child note, as otherwise
|
|
// we'd end up doubling up on notes.
|
|
if (!info->IsChildNote) {
|
|
extendedChildNotes = getGeneratedSourceBufferNotes(info->Loc);
|
|
}
|
|
if (!extendedChildNotes.empty()) {
|
|
extendedChildNotes.insert(extendedChildNotes.end(),
|
|
childNotes.begin(), childNotes.end());
|
|
childNotes = extendedChildNotes;
|
|
}
|
|
|
|
SmallVector<DiagnosticInfo, 1> childInfo;
|
|
for (unsigned i : indices(childNotes)) {
|
|
auto child =
|
|
diagnosticInfoForDiagnostic(childNotes[i],
|
|
/* includeDiagnosticName= */ true);
|
|
assert(child || state.getSuppressNotes());
|
|
if (!child)
|
|
continue;
|
|
assert(child->Kind == DiagnosticKind::Note &&
|
|
"Expected child diagnostics to all be notes?!");
|
|
childInfo.push_back(*child);
|
|
}
|
|
TinyPtrVector<DiagnosticInfo *> childInfoPtrs;
|
|
for (unsigned i : indices(childInfo)) {
|
|
childInfoPtrs.push_back(&childInfo[i]);
|
|
}
|
|
info->ChildDiagnosticInfo = childInfoPtrs;
|
|
|
|
// Capture information about the diagnostic group and its documentation
|
|
// URL. Build the full category chain (leaf + parents).
|
|
auto groupID = diagnostic.getGroupID();
|
|
if (groupID != DiagGroupID::no_group) {
|
|
const auto &diagGroup = getDiagGroupInfoByID(groupID);
|
|
|
|
auto getDiagnosticGroupDocURL =
|
|
[&](const DiagGroupInfo &diagGroup) -> std::string {
|
|
if (diagGroup.toolchainLocalDocumentation) {
|
|
std::string localPath(getLocalDiagnosticDocumentationPath());
|
|
if (!localPath.empty()) {
|
|
if (localPath.back() != '/')
|
|
localPath += "/";
|
|
localPath += diagGroup.documentationFile;
|
|
localPath += ".md";
|
|
return localPath;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string docURL(getDiagnosticDocumentationPath());
|
|
if (!docURL.empty() && docURL.back() != '/')
|
|
docURL += "/";
|
|
docURL += diagGroup.documentationFile;
|
|
return docURL;
|
|
};
|
|
|
|
// Set the leaf category's documentation URL.
|
|
info->setCategoryDocumentationURL(getDiagnosticGroupDocURL(diagGroup));
|
|
|
|
// Append parent groups by walking up supergroups.
|
|
auto currentID = groupID;
|
|
while (true) {
|
|
const auto ¤t = getDiagGroupInfoByID(currentID);
|
|
if (current.supergroups.empty())
|
|
break;
|
|
auto parentID = current.supergroups[0];
|
|
const auto &parent = getDiagGroupInfoByID(parentID);
|
|
std::string parentDocURL(getDiagnosticDocumentationPath());
|
|
if (!parentDocURL.empty() && parentDocURL.back() != '/')
|
|
parentDocURL += "/";
|
|
parentDocURL += parent.documentationFile;
|
|
info->CategoryChain.push_back(
|
|
{StringRef(parent.name), std::move(parentDocURL)});
|
|
currentID = parentID;
|
|
}
|
|
}
|
|
|
|
for (auto &consumer : Consumers) {
|
|
consumer->handleDiagnostic(SourceMgr, *info);
|
|
}
|
|
}
|
|
|
|
// For compatibility with DiagnosticConsumers which don't know about child
|
|
// notes. These can be ignored by consumers which do take advantage of the
|
|
// grouping.
|
|
for (auto &childNote : childNotes)
|
|
emitDiagnostic(childNote);
|
|
}
|
|
|
|
DiagnosticKind DiagnosticEngine::declaredDiagnosticKindFor(const DiagID id) {
|
|
return storedDiagnosticInfos[(unsigned)id].kind;
|
|
}
|
|
|
|
llvm::StringRef DiagnosticEngine::getFormatStringForDiagnostic(DiagID id) {
|
|
llvm::StringRef message = diagnosticStrings[(unsigned)id];
|
|
if (auto localizationProducer = localization.get()) {
|
|
message = localizationProducer->getMessageOr(id, message);
|
|
}
|
|
return message;
|
|
}
|
|
|
|
llvm::StringRef
|
|
DiagnosticEngine::getFormatStringForDiagnostic(const Diagnostic &diagnostic,
|
|
bool includeDiagnosticName) {
|
|
auto diagID = diagnostic.getID();
|
|
auto message = getFormatStringForDiagnostic(diagID);
|
|
|
|
if (!includeDiagnosticName) {
|
|
return message;
|
|
}
|
|
|
|
auto formatMessageWithName = [&](StringRef message, StringRef name) {
|
|
const int additionalCharsLength = 3; // ' ', '[', ']'
|
|
std::string messageWithName;
|
|
messageWithName.reserve(message.size() + name.size() +
|
|
additionalCharsLength);
|
|
messageWithName += message;
|
|
messageWithName += " [";
|
|
messageWithName += name;
|
|
messageWithName += "]";
|
|
return DiagnosticStringsSaver.save(messageWithName);
|
|
};
|
|
switch (printDiagnosticNamesMode) {
|
|
case PrintDiagnosticNamesMode::None:
|
|
case PrintDiagnosticNamesMode::Group:
|
|
break;
|
|
case PrintDiagnosticNamesMode::Identifier: {
|
|
auto userFacingID = diagID;
|
|
// If this diagnostic wraps another one, use the ID of the wrapped
|
|
// diagnostic.
|
|
if (auto wrapped = diagnostic.getWrappedDiagnostic()) {
|
|
userFacingID = wrapped.value()->ID;
|
|
}
|
|
|
|
message =
|
|
formatMessageWithName(message, diagnosticIDStringFor(userFacingID));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
llvm::StringRef
|
|
DiagnosticEngine::diagnosticIDStringFor(const DiagID id) {
|
|
return diagnosticIDStrings[(unsigned)id];
|
|
}
|
|
|
|
const char *InFlightDiagnostic::fixItStringFor(const FixItID id) {
|
|
return fixItStrings[(unsigned)id];
|
|
}
|
|
|
|
void DiagnosticEngine::setBufferIndirectlyCausingDiagnosticToInput(
|
|
SourceLoc loc) {
|
|
// If in the future, nested BufferIndirectlyCausingDiagnosticRAII need be
|
|
// supported, the compiler will need a stack for
|
|
// bufferIndirectlyCausingDiagnostic.
|
|
assert(bufferIndirectlyCausingDiagnostic.isInvalid() &&
|
|
"Buffer should not already be set.");
|
|
bufferIndirectlyCausingDiagnostic = loc;
|
|
assert(bufferIndirectlyCausingDiagnostic.isValid() &&
|
|
"Buffer must be valid for previous assertion to work.");
|
|
}
|
|
|
|
void DiagnosticEngine::resetBufferIndirectlyCausingDiagnostic() {
|
|
bufferIndirectlyCausingDiagnostic = SourceLoc();
|
|
}
|
|
|
|
DiagnosticSuppression::DiagnosticSuppression(DiagnosticEngine &diags)
|
|
: diags(diags)
|
|
{
|
|
consumers = diags.takeConsumers();
|
|
}
|
|
|
|
DiagnosticSuppression::~DiagnosticSuppression() {
|
|
for (auto consumer : consumers)
|
|
diags.addConsumer(*consumer);
|
|
}
|
|
|
|
bool DiagnosticSuppression::isEnabled(const DiagnosticEngine &diags) {
|
|
return diags.getConsumers().empty();
|
|
}
|
|
|
|
BufferIndirectlyCausingDiagnosticRAII::BufferIndirectlyCausingDiagnosticRAII(
|
|
const SourceFile &SF)
|
|
: Diags(SF.getASTContext().Diags) {
|
|
auto id = SF.getBufferID();
|
|
auto loc = SF.getASTContext().SourceMgr.getLocForBufferStart(id);
|
|
if (loc.isValid())
|
|
Diags.setBufferIndirectlyCausingDiagnosticToInput(loc);
|
|
}
|
|
|
|
void DiagnosticEngine::onTentativeDiagnosticFlush(Diagnostic &diagnostic) {
|
|
for (auto &argument : diagnostic.Args) {
|
|
if (argument.getKind() != DiagnosticArgumentKind::String)
|
|
continue;
|
|
|
|
auto content = argument.getAsString();
|
|
if (content.empty())
|
|
continue;
|
|
|
|
auto I = TransactionStrings.insert(content).first;
|
|
argument = DiagnosticArgument(StringRef(I->getKeyData()));
|
|
}
|
|
}
|
|
|
|
DiagnosticQueue::DiagnosticQueue(DiagnosticEngine &engine,
|
|
bool emitOnDestruction)
|
|
: UnderlyingEngine(engine), QueueEngine(engine.SourceMgr),
|
|
EmitOnDestruction(emitOnDestruction) {
|
|
// Open a transaction to avoid emitting any diagnostics for the temporary
|
|
// engine.
|
|
QueueEngine.TransactionCount++;
|
|
|
|
QueueEngine.setLanguageVersion(engine.languageVersion);
|
|
}
|
|
|
|
void DiagnosticQueue::forEach(
|
|
llvm::function_ref<void(const Diagnostic &)> body) const {
|
|
for (auto &activeDiag : QueueEngine.TentativeDiagnostics)
|
|
body(activeDiag.Diag);
|
|
}
|
|
|
|
void DiagnosticQueue::filter(
|
|
llvm::function_ref<bool(const Diagnostic &)> predicate) {
|
|
llvm::erase_if(QueueEngine.TentativeDiagnostics,
|
|
[&](detail::ActiveDiagnostic &activeDiag) {
|
|
return !predicate(activeDiag.Diag);
|
|
});
|
|
}
|
|
|
|
EncodedDiagnosticMessage::EncodedDiagnosticMessage(StringRef S)
|
|
: Message(Lexer::getEncodedStringSegment(S, Buf, /*IsFirstSegment=*/true,
|
|
/*IsLastSegment=*/true,
|
|
/*IndentToStrip=*/~0U)) {}
|