mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Teach the diagnostic engine about a new wrapper type `FullyQualified<T>`. The intent is to clarify which parameters will be printed with additional qualification in diagnostic text. For now, this is only useful for types, so there's a bit of SFINAE guarding against misuse of the abstraction. rdar://63199963
1204 lines
41 KiB
C++
1204 lines
41 KiB
C++
//===--- DiagnosticEngine.h - Diagnostic Display Engine ---------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 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 declares the DiagnosticEngine class, which manages any diagnostics
|
|
// emitted by Swift.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_BASIC_DIAGNOSTICENGINE_H
|
|
#define SWIFT_BASIC_DIAGNOSTICENGINE_H
|
|
|
|
#include "swift/AST/ActorIsolation.h"
|
|
#include "swift/AST/DeclNameLoc.h"
|
|
#include "swift/AST/DiagnosticConsumer.h"
|
|
#include "swift/AST/TypeLoc.h"
|
|
#include "swift/Localization/LocalizationFormat.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/Support/Allocator.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/VersionTuple.h"
|
|
|
|
namespace swift {
|
|
class Decl;
|
|
class DeclAttribute;
|
|
class DiagnosticEngine;
|
|
class SourceManager;
|
|
class ValueDecl;
|
|
class SourceFile;
|
|
|
|
enum class PatternKind : uint8_t;
|
|
enum class SelfAccessKind : uint8_t;
|
|
enum class ReferenceOwnership : uint8_t;
|
|
enum class StaticSpellingKind : uint8_t;
|
|
enum class DescriptiveDeclKind : uint8_t;
|
|
enum DeclAttrKind : unsigned;
|
|
|
|
/// Enumeration describing all of possible diagnostics.
|
|
///
|
|
/// Each of the diagnostics described in Diagnostics.def has an entry in
|
|
/// this enumeration type that uniquely identifies it.
|
|
enum class DiagID : uint32_t;
|
|
|
|
/// Describes a diagnostic along with its argument types.
|
|
///
|
|
/// The diagnostics header introduces instances of this type for each
|
|
/// diagnostic, which provide both the set of argument types (used to
|
|
/// check/convert the arguments at each call site) and the diagnostic ID
|
|
/// (for other information about the diagnostic).
|
|
template<typename ...ArgTypes>
|
|
struct Diag {
|
|
/// The diagnostic ID corresponding to this diagnostic.
|
|
DiagID ID;
|
|
};
|
|
|
|
namespace detail {
|
|
/// Describes how to pass a diagnostic argument of the given type.
|
|
///
|
|
/// By default, diagnostic arguments are passed by value, because they
|
|
/// tend to be small. Larger diagnostic arguments
|
|
/// need to specialize this class template to pass by reference.
|
|
template<typename T>
|
|
struct PassArgument {
|
|
typedef T type;
|
|
};
|
|
}
|
|
|
|
/// A family of wrapper types for compiler data types that forces its
|
|
/// underlying data to be formatted with full qualification.
|
|
///
|
|
/// So far, this is only useful for \c Type, hence the SFINAE'ing.
|
|
template <typename T, typename = void> struct FullyQualified {};
|
|
|
|
template <typename T>
|
|
struct FullyQualified<
|
|
T, typename std::enable_if<std::is_convertible<T, Type>::value>::type> {
|
|
Type t;
|
|
|
|
public:
|
|
FullyQualified(T t) : t(t){};
|
|
|
|
Type getType() const { return t; }
|
|
};
|
|
|
|
/// Describes the kind of diagnostic argument we're storing.
|
|
///
|
|
enum class DiagnosticArgumentKind {
|
|
String,
|
|
Integer,
|
|
Unsigned,
|
|
Identifier,
|
|
ObjCSelector,
|
|
ValueDecl,
|
|
Type,
|
|
TypeRepr,
|
|
FullyQualifiedType,
|
|
PatternKind,
|
|
SelfAccessKind,
|
|
ReferenceOwnership,
|
|
StaticSpellingKind,
|
|
DescriptiveDeclKind,
|
|
DeclAttribute,
|
|
VersionTuple,
|
|
LayoutConstraint,
|
|
ActorIsolation,
|
|
};
|
|
|
|
namespace diag {
|
|
enum class RequirementKind : uint8_t;
|
|
}
|
|
|
|
/// Variant type that holds a single diagnostic argument of a known
|
|
/// type.
|
|
///
|
|
/// All diagnostic arguments are converted to an instance of this class.
|
|
class DiagnosticArgument {
|
|
DiagnosticArgumentKind Kind;
|
|
union {
|
|
int IntegerVal;
|
|
unsigned UnsignedVal;
|
|
StringRef StringVal;
|
|
DeclNameRef IdentifierVal;
|
|
ObjCSelector ObjCSelectorVal;
|
|
ValueDecl *TheValueDecl;
|
|
Type TypeVal;
|
|
TypeRepr *TyR;
|
|
FullyQualified<Type> FullyQualifiedTypeVal;
|
|
PatternKind PatternKindVal;
|
|
SelfAccessKind SelfAccessKindVal;
|
|
ReferenceOwnership ReferenceOwnershipVal;
|
|
StaticSpellingKind StaticSpellingKindVal;
|
|
DescriptiveDeclKind DescriptiveDeclKindVal;
|
|
const DeclAttribute *DeclAttributeVal;
|
|
llvm::VersionTuple VersionVal;
|
|
LayoutConstraint LayoutConstraintVal;
|
|
ActorIsolation ActorIsolationVal;
|
|
};
|
|
|
|
public:
|
|
DiagnosticArgument(StringRef S)
|
|
: Kind(DiagnosticArgumentKind::String), StringVal(S) {
|
|
}
|
|
|
|
DiagnosticArgument(int I)
|
|
: Kind(DiagnosticArgumentKind::Integer), IntegerVal(I) {
|
|
}
|
|
|
|
DiagnosticArgument(unsigned I)
|
|
: Kind(DiagnosticArgumentKind::Unsigned), UnsignedVal(I) {
|
|
}
|
|
|
|
DiagnosticArgument(DeclNameRef R)
|
|
: Kind(DiagnosticArgumentKind::Identifier), IdentifierVal(R) {}
|
|
|
|
DiagnosticArgument(DeclName D)
|
|
: Kind(DiagnosticArgumentKind::Identifier),
|
|
IdentifierVal(DeclNameRef(D)) {}
|
|
|
|
DiagnosticArgument(DeclBaseName D)
|
|
: Kind(DiagnosticArgumentKind::Identifier),
|
|
IdentifierVal(DeclNameRef(D)) {}
|
|
|
|
DiagnosticArgument(Identifier I)
|
|
: Kind(DiagnosticArgumentKind::Identifier),
|
|
IdentifierVal(DeclNameRef(I)) {
|
|
}
|
|
|
|
DiagnosticArgument(ObjCSelector S)
|
|
: Kind(DiagnosticArgumentKind::ObjCSelector), ObjCSelectorVal(S) {
|
|
}
|
|
|
|
DiagnosticArgument(ValueDecl *VD)
|
|
: Kind(DiagnosticArgumentKind::ValueDecl), TheValueDecl(VD) {
|
|
}
|
|
|
|
DiagnosticArgument(Type T)
|
|
: Kind(DiagnosticArgumentKind::Type), TypeVal(T) {
|
|
}
|
|
|
|
DiagnosticArgument(TypeRepr *T)
|
|
: Kind(DiagnosticArgumentKind::TypeRepr), TyR(T) {
|
|
}
|
|
|
|
DiagnosticArgument(FullyQualified<Type> FQT)
|
|
: Kind(DiagnosticArgumentKind::FullyQualifiedType),
|
|
FullyQualifiedTypeVal(FQT) {}
|
|
|
|
DiagnosticArgument(const TypeLoc &TL) {
|
|
if (TypeRepr *tyR = TL.getTypeRepr()) {
|
|
Kind = DiagnosticArgumentKind::TypeRepr;
|
|
TyR = tyR;
|
|
} else {
|
|
Kind = DiagnosticArgumentKind::Type;
|
|
TypeVal = TL.getType();
|
|
}
|
|
}
|
|
|
|
DiagnosticArgument(PatternKind K)
|
|
: Kind(DiagnosticArgumentKind::PatternKind), PatternKindVal(K) {}
|
|
|
|
DiagnosticArgument(ReferenceOwnership RO)
|
|
: Kind(DiagnosticArgumentKind::ReferenceOwnership),
|
|
ReferenceOwnershipVal(RO) {}
|
|
|
|
DiagnosticArgument(SelfAccessKind SAK)
|
|
: Kind(DiagnosticArgumentKind::SelfAccessKind),
|
|
SelfAccessKindVal(SAK) {}
|
|
|
|
DiagnosticArgument(StaticSpellingKind SSK)
|
|
: Kind(DiagnosticArgumentKind::StaticSpellingKind),
|
|
StaticSpellingKindVal(SSK) {}
|
|
|
|
DiagnosticArgument(DescriptiveDeclKind DDK)
|
|
: Kind(DiagnosticArgumentKind::DescriptiveDeclKind),
|
|
DescriptiveDeclKindVal(DDK) {}
|
|
|
|
DiagnosticArgument(const DeclAttribute *attr)
|
|
: Kind(DiagnosticArgumentKind::DeclAttribute),
|
|
DeclAttributeVal(attr) {}
|
|
|
|
DiagnosticArgument(llvm::VersionTuple version)
|
|
: Kind(DiagnosticArgumentKind::VersionTuple),
|
|
VersionVal(version) { }
|
|
|
|
DiagnosticArgument(LayoutConstraint L)
|
|
: Kind(DiagnosticArgumentKind::LayoutConstraint), LayoutConstraintVal(L) {
|
|
}
|
|
|
|
DiagnosticArgument(ActorIsolation AI)
|
|
: Kind(DiagnosticArgumentKind::ActorIsolation),
|
|
ActorIsolationVal(AI) {
|
|
}
|
|
|
|
/// Initializes a diagnostic argument using the underlying type of the
|
|
/// given enum.
|
|
template<
|
|
typename EnumType,
|
|
typename std::enable_if<std::is_enum<EnumType>::value>::type* = nullptr>
|
|
DiagnosticArgument(EnumType value)
|
|
: DiagnosticArgument(
|
|
static_cast<typename std::underlying_type<EnumType>::type>(value)) {}
|
|
|
|
DiagnosticArgumentKind getKind() const { return Kind; }
|
|
|
|
StringRef getAsString() const {
|
|
assert(Kind == DiagnosticArgumentKind::String);
|
|
return StringVal;
|
|
}
|
|
|
|
int getAsInteger() const {
|
|
assert(Kind == DiagnosticArgumentKind::Integer);
|
|
return IntegerVal;
|
|
}
|
|
|
|
unsigned getAsUnsigned() const {
|
|
assert(Kind == DiagnosticArgumentKind::Unsigned);
|
|
return UnsignedVal;
|
|
}
|
|
|
|
DeclNameRef getAsIdentifier() const {
|
|
assert(Kind == DiagnosticArgumentKind::Identifier);
|
|
return IdentifierVal;
|
|
}
|
|
|
|
ObjCSelector getAsObjCSelector() const {
|
|
assert(Kind == DiagnosticArgumentKind::ObjCSelector);
|
|
return ObjCSelectorVal;
|
|
}
|
|
|
|
ValueDecl *getAsValueDecl() const {
|
|
assert(Kind == DiagnosticArgumentKind::ValueDecl);
|
|
return TheValueDecl;
|
|
}
|
|
|
|
Type getAsType() const {
|
|
assert(Kind == DiagnosticArgumentKind::Type);
|
|
return TypeVal;
|
|
}
|
|
|
|
TypeRepr *getAsTypeRepr() const {
|
|
assert(Kind == DiagnosticArgumentKind::TypeRepr);
|
|
return TyR;
|
|
}
|
|
|
|
FullyQualified<Type> getAsFullyQualifiedType() const {
|
|
assert(Kind == DiagnosticArgumentKind::FullyQualifiedType);
|
|
return FullyQualifiedTypeVal;
|
|
}
|
|
|
|
PatternKind getAsPatternKind() const {
|
|
assert(Kind == DiagnosticArgumentKind::PatternKind);
|
|
return PatternKindVal;
|
|
}
|
|
|
|
ReferenceOwnership getAsReferenceOwnership() const {
|
|
assert(Kind == DiagnosticArgumentKind::ReferenceOwnership);
|
|
return ReferenceOwnershipVal;
|
|
}
|
|
|
|
SelfAccessKind getAsSelfAccessKind() const {
|
|
assert(Kind == DiagnosticArgumentKind::SelfAccessKind);
|
|
return SelfAccessKindVal;
|
|
}
|
|
|
|
StaticSpellingKind getAsStaticSpellingKind() const {
|
|
assert(Kind == DiagnosticArgumentKind::StaticSpellingKind);
|
|
return StaticSpellingKindVal;
|
|
}
|
|
|
|
DescriptiveDeclKind getAsDescriptiveDeclKind() const {
|
|
assert(Kind == DiagnosticArgumentKind::DescriptiveDeclKind);
|
|
return DescriptiveDeclKindVal;
|
|
}
|
|
|
|
const DeclAttribute *getAsDeclAttribute() const {
|
|
assert(Kind == DiagnosticArgumentKind::DeclAttribute);
|
|
return DeclAttributeVal;
|
|
}
|
|
|
|
llvm::VersionTuple getAsVersionTuple() const {
|
|
assert(Kind == DiagnosticArgumentKind::VersionTuple);
|
|
return VersionVal;
|
|
}
|
|
|
|
LayoutConstraint getAsLayoutConstraint() const {
|
|
assert(Kind == DiagnosticArgumentKind::LayoutConstraint);
|
|
return LayoutConstraintVal;
|
|
}
|
|
|
|
ActorIsolation getAsActorIsolation() const {
|
|
assert(Kind == DiagnosticArgumentKind::ActorIsolation);
|
|
return ActorIsolationVal;
|
|
}
|
|
};
|
|
|
|
struct DiagnosticFormatOptions {
|
|
const std::string OpeningQuotationMark;
|
|
const std::string ClosingQuotationMark;
|
|
const std::string AKAFormatString;
|
|
const std::string OpaqueResultFormatString;
|
|
|
|
DiagnosticFormatOptions(std::string OpeningQuotationMark,
|
|
std::string ClosingQuotationMark,
|
|
std::string AKAFormatString,
|
|
std::string OpaqueResultFormatString)
|
|
: OpeningQuotationMark(OpeningQuotationMark),
|
|
ClosingQuotationMark(ClosingQuotationMark),
|
|
AKAFormatString(AKAFormatString),
|
|
OpaqueResultFormatString(OpaqueResultFormatString) {}
|
|
|
|
DiagnosticFormatOptions()
|
|
: OpeningQuotationMark("'"), ClosingQuotationMark("'"),
|
|
AKAFormatString("'%s' (aka '%s')"),
|
|
OpaqueResultFormatString("'%s' (%s of '%s')") {}
|
|
|
|
/// When formatting fix-it arguments, don't include quotes or other
|
|
/// additions which would result in invalid code.
|
|
static DiagnosticFormatOptions formatForFixIts() {
|
|
return DiagnosticFormatOptions("", "", "%s", "%s");
|
|
}
|
|
};
|
|
|
|
enum class FixItID : uint32_t;
|
|
|
|
/// Represents a fix-it defined with a format string and optional
|
|
/// DiagnosticArguments. The template parameters allow the
|
|
/// fixIt... methods on InFlightDiagnostic to infer their own
|
|
/// template params.
|
|
template <typename... ArgTypes> struct StructuredFixIt { FixItID ID; };
|
|
|
|
/// Diagnostic - This is a specific instance of a diagnostic along with all of
|
|
/// the DiagnosticArguments that it requires.
|
|
class Diagnostic {
|
|
public:
|
|
typedef DiagnosticInfo::FixIt FixIt;
|
|
|
|
private:
|
|
DiagID ID;
|
|
SmallVector<DiagnosticArgument, 3> Args;
|
|
SmallVector<CharSourceRange, 2> Ranges;
|
|
SmallVector<FixIt, 2> FixIts;
|
|
std::vector<Diagnostic> ChildNotes;
|
|
SourceLoc Loc;
|
|
bool IsChildNote = false;
|
|
const swift::Decl *Decl = nullptr;
|
|
|
|
friend DiagnosticEngine;
|
|
|
|
public:
|
|
// All constructors are intentionally implicit.
|
|
template<typename ...ArgTypes>
|
|
Diagnostic(Diag<ArgTypes...> ID,
|
|
typename detail::PassArgument<ArgTypes>::type... VArgs)
|
|
: ID(ID.ID) {
|
|
DiagnosticArgument DiagArgs[] = {
|
|
DiagnosticArgument(0), std::move(VArgs)...
|
|
};
|
|
Args.append(DiagArgs + 1, DiagArgs + 1 + sizeof...(VArgs));
|
|
}
|
|
|
|
/*implicit*/Diagnostic(DiagID ID, ArrayRef<DiagnosticArgument> Args)
|
|
: ID(ID), Args(Args.begin(), Args.end()) {}
|
|
|
|
// Accessors.
|
|
DiagID getID() const { return ID; }
|
|
ArrayRef<DiagnosticArgument> getArgs() const { return Args; }
|
|
ArrayRef<CharSourceRange> getRanges() const { return Ranges; }
|
|
ArrayRef<FixIt> getFixIts() const { return FixIts; }
|
|
ArrayRef<Diagnostic> getChildNotes() const { return ChildNotes; }
|
|
bool isChildNote() const { return IsChildNote; }
|
|
SourceLoc getLoc() const { return Loc; }
|
|
const class Decl *getDecl() const { return Decl; }
|
|
|
|
void setLoc(SourceLoc loc) { Loc = loc; }
|
|
void setIsChildNote(bool isChildNote) { IsChildNote = isChildNote; }
|
|
void setDecl(const class Decl *decl) { Decl = decl; }
|
|
|
|
/// Returns true if this object represents a particular diagnostic.
|
|
///
|
|
/// \code
|
|
/// someDiag.is(diag::invalid_diagnostic)
|
|
/// \endcode
|
|
template<typename ...OtherArgTypes>
|
|
bool is(Diag<OtherArgTypes...> Other) const {
|
|
return ID == Other.ID;
|
|
}
|
|
|
|
void addRange(CharSourceRange R) {
|
|
Ranges.push_back(R);
|
|
}
|
|
|
|
// Avoid copying the fix-it text more than necessary.
|
|
void addFixIt(FixIt &&F) {
|
|
FixIts.push_back(std::move(F));
|
|
}
|
|
|
|
void addChildNote(Diagnostic &&D);
|
|
};
|
|
|
|
/// Describes an in-flight diagnostic, which is currently active
|
|
/// within the diagnostic engine and can be augmented within additional
|
|
/// information (source ranges, Fix-Its, etc.).
|
|
///
|
|
/// Only a single in-flight diagnostic can be active at one time, and all
|
|
/// additional information must be emitted through the active in-flight
|
|
/// diagnostic.
|
|
class InFlightDiagnostic {
|
|
friend class DiagnosticEngine;
|
|
|
|
DiagnosticEngine *Engine;
|
|
bool IsActive;
|
|
|
|
/// Create a new in-flight diagnostic.
|
|
///
|
|
/// This constructor is only available to the DiagnosticEngine.
|
|
InFlightDiagnostic(DiagnosticEngine &Engine)
|
|
: Engine(&Engine), IsActive(true) { }
|
|
|
|
InFlightDiagnostic(const InFlightDiagnostic &) = delete;
|
|
InFlightDiagnostic &operator=(const InFlightDiagnostic &) = delete;
|
|
InFlightDiagnostic &operator=(InFlightDiagnostic &&) = delete;
|
|
|
|
public:
|
|
/// Create an active but unattached in-flight diagnostic.
|
|
///
|
|
/// The resulting diagnostic can be used as a dummy, accepting the
|
|
/// syntax to add additional information to a diagnostic without
|
|
/// actually emitting a diagnostic.
|
|
InFlightDiagnostic() : Engine(0), IsActive(true) { }
|
|
|
|
/// Transfer an in-flight diagnostic to a new object, which is
|
|
/// typically used when returning in-flight diagnostics.
|
|
InFlightDiagnostic(InFlightDiagnostic &&Other)
|
|
: Engine(Other.Engine), IsActive(Other.IsActive) {
|
|
Other.IsActive = false;
|
|
}
|
|
|
|
~InFlightDiagnostic() {
|
|
if (IsActive)
|
|
flush();
|
|
}
|
|
|
|
/// Flush the active diagnostic to the diagnostic output engine.
|
|
void flush();
|
|
|
|
/// Add a token-based range to the currently-active diagnostic.
|
|
InFlightDiagnostic &highlight(SourceRange R);
|
|
|
|
/// Add a character-based range to the currently-active diagnostic.
|
|
InFlightDiagnostic &highlightChars(SourceLoc Start, SourceLoc End);
|
|
|
|
static const char *fixItStringFor(const FixItID id);
|
|
|
|
/// Add a token-based replacement fix-it to the currently-active
|
|
/// diagnostic.
|
|
template <typename... ArgTypes>
|
|
InFlightDiagnostic &
|
|
fixItReplace(SourceRange R, StructuredFixIt<ArgTypes...> fixIt,
|
|
typename detail::PassArgument<ArgTypes>::type... VArgs) {
|
|
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
|
|
return fixItReplace(R, fixItStringFor(fixIt.ID), DiagArgs);
|
|
}
|
|
|
|
/// Add a character-based replacement fix-it to the currently-active
|
|
/// diagnostic.
|
|
template <typename... ArgTypes>
|
|
InFlightDiagnostic &
|
|
fixItReplaceChars(SourceLoc Start, SourceLoc End,
|
|
StructuredFixIt<ArgTypes...> fixIt,
|
|
typename detail::PassArgument<ArgTypes>::type... VArgs) {
|
|
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
|
|
return fixItReplaceChars(Start, End, fixItStringFor(fixIt.ID), DiagArgs);
|
|
}
|
|
|
|
/// Add an insertion fix-it to the currently-active diagnostic.
|
|
template <typename... ArgTypes>
|
|
InFlightDiagnostic &
|
|
fixItInsert(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
|
|
typename detail::PassArgument<ArgTypes>::type... VArgs) {
|
|
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
|
|
return fixItReplaceChars(L, L, fixItStringFor(fixIt.ID), DiagArgs);
|
|
}
|
|
|
|
/// Add an insertion fix-it to the currently-active diagnostic. The
|
|
/// text is inserted immediately *after* the token specified.
|
|
template <typename... ArgTypes>
|
|
InFlightDiagnostic &
|
|
fixItInsertAfter(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
|
|
typename detail::PassArgument<ArgTypes>::type... VArgs) {
|
|
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
|
|
return fixItInsertAfter(L, fixItStringFor(fixIt.ID), DiagArgs);
|
|
}
|
|
|
|
/// Add a token-based replacement fix-it to the currently-active
|
|
/// diagnostic.
|
|
InFlightDiagnostic &fixItReplace(SourceRange R, StringRef Str);
|
|
|
|
/// Add a character-based replacement fix-it to the currently-active
|
|
/// diagnostic.
|
|
InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
|
|
StringRef Str) {
|
|
return fixItReplaceChars(Start, End, "%0", {Str});
|
|
}
|
|
|
|
/// Add an insertion fix-it to the currently-active diagnostic.
|
|
InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef Str) {
|
|
return fixItReplaceChars(L, L, "%0", {Str});
|
|
}
|
|
|
|
/// Add an insertion fix-it to the currently-active diagnostic. The
|
|
/// text is inserted immediately *after* the token specified.
|
|
InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef Str) {
|
|
return fixItInsertAfter(L, "%0", {Str});
|
|
}
|
|
|
|
/// Add a token-based removal fix-it to the currently-active
|
|
/// diagnostic.
|
|
InFlightDiagnostic &fixItRemove(SourceRange R);
|
|
|
|
/// Add a character-based removal fix-it to the currently-active
|
|
/// diagnostic.
|
|
InFlightDiagnostic &fixItRemoveChars(SourceLoc Start, SourceLoc End) {
|
|
return fixItReplaceChars(Start, End, {});
|
|
}
|
|
|
|
/// Add two replacement fix-it exchanging source ranges to the
|
|
/// currently-active diagnostic.
|
|
InFlightDiagnostic &fixItExchange(SourceRange R1, SourceRange R2);
|
|
|
|
private:
|
|
InFlightDiagnostic &fixItReplace(SourceRange R, StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args);
|
|
|
|
InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
|
|
StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args);
|
|
|
|
InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
return fixItReplaceChars(L, L, FormatString, Args);
|
|
}
|
|
|
|
InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef FormatString,
|
|
ArrayRef<DiagnosticArgument> Args);
|
|
};
|
|
|
|
/// Class to track, map, and remap diagnostic severity and fatality
|
|
///
|
|
class DiagnosticState {
|
|
public:
|
|
/// Describes the current behavior to take with a diagnostic
|
|
enum class Behavior : uint8_t {
|
|
Unspecified,
|
|
Ignore,
|
|
Note,
|
|
Remark,
|
|
Warning,
|
|
Error,
|
|
Fatal,
|
|
};
|
|
|
|
private:
|
|
/// Whether we should continue to emit diagnostics, even after a
|
|
/// fatal error
|
|
bool showDiagnosticsAfterFatalError = false;
|
|
|
|
/// Don't emit any warnings
|
|
bool suppressWarnings = false;
|
|
|
|
/// Emit all warnings as errors
|
|
bool warningsAsErrors = false;
|
|
|
|
/// Whether a fatal error has occurred
|
|
bool fatalErrorOccurred = false;
|
|
|
|
/// Whether any error diagnostics have been emitted.
|
|
bool anyErrorOccurred = false;
|
|
|
|
/// Track the previous emitted Behavior, useful for notes
|
|
Behavior previousBehavior = Behavior::Unspecified;
|
|
|
|
/// Track settable, per-diagnostic state that we store
|
|
std::vector<Behavior> perDiagnosticBehavior;
|
|
|
|
public:
|
|
DiagnosticState();
|
|
|
|
/// Figure out the Behavior for the given diagnostic, taking current
|
|
/// state such as fatality into account.
|
|
Behavior determineBehavior(DiagID id);
|
|
|
|
bool hadAnyError() const { return anyErrorOccurred; }
|
|
bool hasFatalErrorOccurred() const { return fatalErrorOccurred; }
|
|
|
|
void setShowDiagnosticsAfterFatalError(bool val = true) {
|
|
showDiagnosticsAfterFatalError = val;
|
|
}
|
|
bool getShowDiagnosticsAfterFatalError() {
|
|
return showDiagnosticsAfterFatalError;
|
|
}
|
|
|
|
/// Whether to skip emitting warnings
|
|
void setSuppressWarnings(bool val) { suppressWarnings = val; }
|
|
bool getSuppressWarnings() const { return suppressWarnings; }
|
|
|
|
/// Whether to treat warnings as errors
|
|
void setWarningsAsErrors(bool val) { warningsAsErrors = val; }
|
|
bool getWarningsAsErrors() const { return warningsAsErrors; }
|
|
|
|
void resetHadAnyError() {
|
|
anyErrorOccurred = false;
|
|
fatalErrorOccurred = false;
|
|
}
|
|
|
|
/// Set per-diagnostic behavior
|
|
void setDiagnosticBehavior(DiagID id, Behavior behavior) {
|
|
perDiagnosticBehavior[(unsigned)id] = behavior;
|
|
}
|
|
|
|
private:
|
|
// Make the state movable only
|
|
DiagnosticState(const DiagnosticState &) = delete;
|
|
const DiagnosticState &operator=(const DiagnosticState &) = delete;
|
|
|
|
DiagnosticState(DiagnosticState &&) = default;
|
|
DiagnosticState &operator=(DiagnosticState &&) = default;
|
|
};
|
|
|
|
/// Class responsible for formatting diagnostics and presenting them
|
|
/// to the user.
|
|
class DiagnosticEngine {
|
|
public:
|
|
/// The source manager used to interpret source locations and
|
|
/// display diagnostics.
|
|
SourceManager &SourceMgr;
|
|
|
|
private:
|
|
/// The diagnostic consumer(s) that will be responsible for actually
|
|
/// emitting diagnostics.
|
|
SmallVector<DiagnosticConsumer *, 2> Consumers;
|
|
|
|
/// Tracks diagnostic behaviors and state
|
|
DiagnosticState state;
|
|
|
|
/// The currently active diagnostic, if there is one.
|
|
Optional<Diagnostic> ActiveDiagnostic;
|
|
|
|
/// All diagnostics that have are no longer active but have not yet
|
|
/// been emitted due to an open transaction.
|
|
SmallVector<Diagnostic, 4> TentativeDiagnostics;
|
|
|
|
/// The set of declarations for which we have pretty-printed
|
|
/// results that we can point to on the command line.
|
|
llvm::DenseMap<const Decl *, SourceLoc> PrettyPrintedDeclarations;
|
|
|
|
llvm::BumpPtrAllocator TransactionAllocator;
|
|
/// A set of all strings involved in current transactional chain.
|
|
/// This is required because diagnostics are not directly emitted
|
|
/// but rather stored until all transactions complete.
|
|
llvm::StringSet<llvm::BumpPtrAllocator &> TransactionStrings;
|
|
|
|
/// Diagnostic producer to handle the logic behind retrieving a localized
|
|
/// diagnostic message.
|
|
std::unique_ptr<diag::LocalizationProducer> localization;
|
|
|
|
/// The number of open diagnostic transactions. Diagnostics are only
|
|
/// emitted once all transactions have closed.
|
|
unsigned TransactionCount = 0;
|
|
|
|
/// For batch mode, use this to know where to output a diagnostic from a
|
|
/// non-primary file. It's any location in the buffer of the current primary
|
|
/// input being compiled.
|
|
/// May be invalid.
|
|
SourceLoc bufferIndirectlyCausingDiagnostic;
|
|
|
|
/// Print diagnostic names after their messages
|
|
bool printDiagnosticNames = false;
|
|
|
|
/// Path to diagnostic documentation directory.
|
|
std::string diagnosticDocumentationPath = "";
|
|
|
|
friend class InFlightDiagnostic;
|
|
friend class DiagnosticTransaction;
|
|
friend class CompoundDiagnosticTransaction;
|
|
|
|
public:
|
|
explicit DiagnosticEngine(SourceManager &SourceMgr)
|
|
: SourceMgr(SourceMgr), ActiveDiagnostic(),
|
|
TransactionStrings(TransactionAllocator) {}
|
|
|
|
/// hadAnyError - return true if any *error* diagnostics have been emitted.
|
|
bool hadAnyError() const { return state.hadAnyError(); }
|
|
|
|
bool hasFatalErrorOccurred() const {
|
|
return state.hasFatalErrorOccurred();
|
|
}
|
|
|
|
void setShowDiagnosticsAfterFatalError(bool val = true) {
|
|
state.setShowDiagnosticsAfterFatalError(val);
|
|
}
|
|
bool getShowDiagnosticsAfterFatalError() {
|
|
return state.getShowDiagnosticsAfterFatalError();
|
|
}
|
|
|
|
void flushConsumers() {
|
|
for (auto consumer : Consumers)
|
|
consumer->flush();
|
|
}
|
|
|
|
/// Whether to skip emitting warnings
|
|
void setSuppressWarnings(bool val) { state.setSuppressWarnings(val); }
|
|
bool getSuppressWarnings() const {
|
|
return state.getSuppressWarnings();
|
|
}
|
|
|
|
/// Whether to treat warnings as errors
|
|
void setWarningsAsErrors(bool val) { state.setWarningsAsErrors(val); }
|
|
bool getWarningsAsErrors() const {
|
|
return state.getWarningsAsErrors();
|
|
}
|
|
|
|
/// Whether to print diagnostic names after their messages
|
|
void setPrintDiagnosticNames(bool val) {
|
|
printDiagnosticNames = val;
|
|
}
|
|
bool getPrintDiagnosticNames() const {
|
|
return printDiagnosticNames;
|
|
}
|
|
|
|
void setDiagnosticDocumentationPath(std::string path) {
|
|
diagnosticDocumentationPath = path;
|
|
}
|
|
StringRef getDiagnosticDocumentationPath() {
|
|
return diagnosticDocumentationPath;
|
|
}
|
|
|
|
void setLocalization(std::string locale, std::string path) {
|
|
assert(!locale.empty());
|
|
assert(!path.empty());
|
|
llvm::SmallString<128> filePath(path);
|
|
llvm::sys::path::append(filePath, locale);
|
|
llvm::sys::path::replace_extension(filePath, ".db");
|
|
|
|
// If the serialized diagnostics file not available,
|
|
// fallback to the `YAML` file.
|
|
if (llvm::sys::fs::exists(filePath)) {
|
|
if (auto file = llvm::MemoryBuffer::getFile(filePath)) {
|
|
localization = std::make_unique<diag::SerializedLocalizationProducer>(
|
|
std::move(file.get()));
|
|
}
|
|
} else {
|
|
llvm::sys::path::replace_extension(filePath, ".yaml");
|
|
// In case of missing localization files, we should fallback to messages
|
|
// from `.def` files.
|
|
if (llvm::sys::fs::exists(filePath)) {
|
|
localization =
|
|
std::make_unique<diag::YAMLLocalizationProducer>(filePath.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ignoreDiagnostic(DiagID id) {
|
|
state.setDiagnosticBehavior(id, DiagnosticState::Behavior::Ignore);
|
|
}
|
|
|
|
void resetHadAnyError() {
|
|
state.resetHadAnyError();
|
|
}
|
|
|
|
/// Add an additional DiagnosticConsumer to receive diagnostics.
|
|
void addConsumer(DiagnosticConsumer &Consumer) {
|
|
Consumers.push_back(&Consumer);
|
|
}
|
|
|
|
/// Remove a specific DiagnosticConsumer.
|
|
void removeConsumer(DiagnosticConsumer &Consumer) {
|
|
Consumers.erase(
|
|
std::remove(Consumers.begin(), Consumers.end(), &Consumer));
|
|
}
|
|
|
|
/// Remove and return all \c DiagnosticConsumers.
|
|
std::vector<DiagnosticConsumer *> takeConsumers() {
|
|
auto Result = std::vector<DiagnosticConsumer*>(Consumers.begin(),
|
|
Consumers.end());
|
|
Consumers.clear();
|
|
return Result;
|
|
}
|
|
|
|
/// Return all \c DiagnosticConsumers.
|
|
ArrayRef<DiagnosticConsumer *> getConsumers() const {
|
|
return Consumers;
|
|
}
|
|
|
|
/// Emit a diagnostic using a preformatted array of diagnostic
|
|
/// arguments.
|
|
///
|
|
/// \param Loc The location to which the diagnostic refers in the source
|
|
/// code.
|
|
///
|
|
/// \param ID The diagnostic ID.
|
|
///
|
|
/// \param Args The preformatted set of diagnostic arguments. The caller
|
|
/// must ensure that the diagnostic arguments have the appropriate type.
|
|
///
|
|
/// \returns An in-flight diagnostic, to which additional information can
|
|
/// be attached.
|
|
InFlightDiagnostic diagnose(SourceLoc Loc, DiagID ID,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
return diagnose(Loc, Diagnostic(ID, Args));
|
|
}
|
|
|
|
/// Emit a diagnostic using a preformatted array of diagnostic
|
|
/// arguments.
|
|
///
|
|
/// \param Loc The declaration name location to which the
|
|
/// diagnostic refers in the source code.
|
|
///
|
|
/// \param ID The diagnostic ID.
|
|
///
|
|
/// \param Args The preformatted set of diagnostic arguments. The caller
|
|
/// must ensure that the diagnostic arguments have the appropriate type.
|
|
///
|
|
/// \returns An in-flight diagnostic, to which additional information can
|
|
/// be attached.
|
|
InFlightDiagnostic diagnose(DeclNameLoc Loc, DiagID ID,
|
|
ArrayRef<DiagnosticArgument> Args) {
|
|
return diagnose(Loc.getBaseNameLoc(), Diagnostic(ID, Args));
|
|
}
|
|
|
|
/// Emit an already-constructed diagnostic at the given location.
|
|
///
|
|
/// \param Loc The location to which the diagnostic refers in the source
|
|
/// code.
|
|
///
|
|
/// \param D The diagnostic.
|
|
///
|
|
/// \returns An in-flight diagnostic, to which additional information can
|
|
/// be attached.
|
|
InFlightDiagnostic diagnose(SourceLoc Loc, const Diagnostic &D) {
|
|
assert(!ActiveDiagnostic && "Already have an active diagnostic");
|
|
ActiveDiagnostic = D;
|
|
ActiveDiagnostic->setLoc(Loc);
|
|
return InFlightDiagnostic(*this);
|
|
}
|
|
|
|
/// Emit a diagnostic with the given set of diagnostic arguments.
|
|
///
|
|
/// \param Loc The location to which the diagnostic refers in the source
|
|
/// code.
|
|
///
|
|
/// \param ID The diagnostic to be emitted.
|
|
///
|
|
/// \param Args The diagnostic arguments, which will be converted to
|
|
/// the types expected by the diagnostic \p ID.
|
|
template<typename ...ArgTypes>
|
|
InFlightDiagnostic
|
|
diagnose(SourceLoc Loc, Diag<ArgTypes...> ID,
|
|
typename detail::PassArgument<ArgTypes>::type... Args) {
|
|
return diagnose(Loc, Diagnostic(ID, std::move(Args)...));
|
|
}
|
|
|
|
/// Delete an API that may lead clients to avoid specifying source location.
|
|
template<typename ...ArgTypes>
|
|
InFlightDiagnostic
|
|
diagnose(Diag<ArgTypes...> ID,
|
|
typename detail::PassArgument<ArgTypes>::type... Args) = delete;
|
|
|
|
/// Emit a diagnostic with the given set of diagnostic arguments.
|
|
///
|
|
/// \param Loc The declaration name location to which the
|
|
/// diagnostic refers in the source code.
|
|
///
|
|
/// \param ID The diagnostic to be emitted.
|
|
///
|
|
/// \param Args The diagnostic arguments, which will be converted to
|
|
/// the types expected by the diagnostic \p ID.
|
|
template<typename ...ArgTypes>
|
|
InFlightDiagnostic
|
|
diagnose(DeclNameLoc Loc, Diag<ArgTypes...> ID,
|
|
typename detail::PassArgument<ArgTypes>::type... Args) {
|
|
return diagnose(Loc.getBaseNameLoc(), Diagnostic(ID, std::move(Args)...));
|
|
}
|
|
|
|
/// Emit a diagnostic using a preformatted array of diagnostic
|
|
/// arguments.
|
|
///
|
|
/// \param decl The declaration to which this diagnostic refers, which
|
|
/// may or may not have associated source-location information.
|
|
///
|
|
/// \param id The diagnostic ID.
|
|
///
|
|
/// \param args The preformatted set of diagnostic arguments. The caller
|
|
/// must ensure that the diagnostic arguments have the appropriate type.
|
|
///
|
|
/// \returns An in-flight diagnostic, to which additional information can
|
|
/// be attached.
|
|
InFlightDiagnostic diagnose(const Decl *decl, DiagID id,
|
|
ArrayRef<DiagnosticArgument> args) {
|
|
return diagnose(decl, Diagnostic(id, args));
|
|
}
|
|
|
|
/// Emit an already-constructed diagnostic referencing the given
|
|
/// declaration.
|
|
///
|
|
/// \param decl The declaration to which this diagnostic refers, which
|
|
/// may or may not have associated source-location information.
|
|
///
|
|
/// \param diag The diagnostic.
|
|
///
|
|
/// \returns An in-flight diagnostic, to which additional information can
|
|
/// be attached.
|
|
InFlightDiagnostic diagnose(const Decl *decl, const Diagnostic &diag) {
|
|
assert(!ActiveDiagnostic && "Already have an active diagnostic");
|
|
ActiveDiagnostic = diag;
|
|
ActiveDiagnostic->setDecl(decl);
|
|
return InFlightDiagnostic(*this);
|
|
}
|
|
|
|
/// Emit a diagnostic with the given set of diagnostic arguments.
|
|
///
|
|
/// \param decl The declaration to which this diagnostic refers, which
|
|
/// may or may not have associated source-location information.
|
|
///
|
|
/// \param id The diagnostic to be emitted.
|
|
///
|
|
/// \param args The diagnostic arguments, which will be converted to
|
|
/// the types expected by the diagnostic \p ID.
|
|
template<typename ...ArgTypes>
|
|
InFlightDiagnostic
|
|
diagnose(const Decl *decl, Diag<ArgTypes...> id,
|
|
typename detail::PassArgument<ArgTypes>::type... args) {
|
|
return diagnose(decl, Diagnostic(id, std::move(args)...));
|
|
}
|
|
|
|
/// Emit a parent diagnostic and attached notes.
|
|
///
|
|
/// \param parentDiag An InFlightDiagnostic representing the parent diag.
|
|
///
|
|
/// \param builder A closure which builds and emits notes to be attached to
|
|
/// the parent diag.
|
|
void diagnoseWithNotes(InFlightDiagnostic parentDiag,
|
|
llvm::function_ref<void(void)> builder);
|
|
|
|
/// \returns true if diagnostic is marked with PointsToFirstBadToken
|
|
/// option.
|
|
bool isDiagnosticPointsToFirstBadToken(DiagID id) const;
|
|
|
|
/// \returns true if any diagnostic consumer gave an error while invoking
|
|
//// \c finishProcessing.
|
|
bool finishProcessing();
|
|
|
|
/// Format the given diagnostic text and place the result in the given
|
|
/// buffer.
|
|
static void formatDiagnosticText(
|
|
llvm::raw_ostream &Out, StringRef InText,
|
|
ArrayRef<DiagnosticArgument> FormatArgs,
|
|
DiagnosticFormatOptions FormatOpts = DiagnosticFormatOptions());
|
|
|
|
private:
|
|
/// Called when tentative diagnostic is about to be flushed,
|
|
/// to apply any required transformations e.g. copy string arguments
|
|
/// to extend their lifetime.
|
|
void onTentativeDiagnosticFlush(Diagnostic &diagnostic);
|
|
|
|
/// Flush the active diagnostic.
|
|
void flushActiveDiagnostic();
|
|
|
|
/// Retrieve the active diagnostic.
|
|
Diagnostic &getActiveDiagnostic() { return *ActiveDiagnostic; }
|
|
|
|
/// Generate DiagnosticInfo for a Diagnostic to be passed to consumers.
|
|
Optional<DiagnosticInfo>
|
|
diagnosticInfoForDiagnostic(const Diagnostic &diagnostic);
|
|
|
|
/// Send \c diag to all diagnostic consumers.
|
|
void emitDiagnostic(const Diagnostic &diag);
|
|
|
|
/// Send all tentative diagnostics to all diagnostic consumers and
|
|
/// delete them.
|
|
void emitTentativeDiagnostics();
|
|
|
|
public:
|
|
llvm::StringRef diagnosticStringFor(const DiagID id,
|
|
bool printDiagnosticName);
|
|
|
|
/// If there is no clear .dia file for a diagnostic, put it in the one
|
|
/// corresponding to the SourceLoc given here.
|
|
/// In particular, in batch mode when a diagnostic is located in
|
|
/// a non-primary file, use this affordance to place it in the .dia
|
|
/// file for the primary that is currently being worked on.
|
|
void setBufferIndirectlyCausingDiagnosticToInput(SourceLoc);
|
|
void resetBufferIndirectlyCausingDiagnostic();
|
|
SourceLoc getDefaultDiagnosticLoc() const {
|
|
return bufferIndirectlyCausingDiagnostic;
|
|
}
|
|
};
|
|
|
|
class BufferIndirectlyCausingDiagnosticRAII {
|
|
private:
|
|
DiagnosticEngine &Diags;
|
|
public:
|
|
BufferIndirectlyCausingDiagnosticRAII(const SourceFile &SF);
|
|
~BufferIndirectlyCausingDiagnosticRAII() {
|
|
Diags.resetBufferIndirectlyCausingDiagnostic();
|
|
}
|
|
};
|
|
|
|
/// Represents a diagnostic transaction. While a transaction is
|
|
/// open, all recorded diagnostics are saved until the transaction commits,
|
|
/// at which point they are emitted. If the transaction is instead aborted,
|
|
/// the diagnostics are erased. Transactions may be nested but must be closed
|
|
/// in LIFO order. An open transaction is implicitly committed upon
|
|
/// destruction.
|
|
class DiagnosticTransaction {
|
|
protected:
|
|
DiagnosticEngine &Engine;
|
|
|
|
/// How many tentative diagnostics there were when the transaction
|
|
/// was opened.
|
|
unsigned PrevDiagnostics;
|
|
|
|
/// How many other transactions were open when this transaction was
|
|
/// opened.
|
|
unsigned Depth;
|
|
|
|
/// Whether this transaction is currently open.
|
|
bool IsOpen = true;
|
|
|
|
public:
|
|
DiagnosticTransaction(const DiagnosticTransaction &) = delete;
|
|
DiagnosticTransaction &operator=(const DiagnosticTransaction &) = delete;
|
|
|
|
explicit DiagnosticTransaction(DiagnosticEngine &engine)
|
|
: Engine(engine),
|
|
PrevDiagnostics(Engine.TentativeDiagnostics.size()),
|
|
Depth(Engine.TransactionCount),
|
|
IsOpen(true)
|
|
{
|
|
Engine.TransactionCount++;
|
|
}
|
|
|
|
~DiagnosticTransaction() {
|
|
if (IsOpen) {
|
|
commit();
|
|
}
|
|
|
|
if (Depth == 0) {
|
|
Engine.TransactionStrings.clear();
|
|
Engine.TransactionAllocator.Reset();
|
|
}
|
|
}
|
|
|
|
bool hasErrors() const {
|
|
ArrayRef<Diagnostic> diagnostics(Engine.TentativeDiagnostics.begin() +
|
|
PrevDiagnostics,
|
|
Engine.TentativeDiagnostics.end());
|
|
|
|
for (auto &diagnostic : diagnostics) {
|
|
auto behavior = Engine.state.determineBehavior(diagnostic.getID());
|
|
if (behavior == DiagnosticState::Behavior::Fatal ||
|
|
behavior == DiagnosticState::Behavior::Error)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Abort and close this transaction and erase all diagnostics
|
|
/// record while it was open.
|
|
void abort() {
|
|
close();
|
|
Engine.TentativeDiagnostics.erase(
|
|
Engine.TentativeDiagnostics.begin() + PrevDiagnostics,
|
|
Engine.TentativeDiagnostics.end());
|
|
}
|
|
|
|
/// Commit and close this transaction. If this is the top-level
|
|
/// transaction, emit any diagnostics that were recorded while it was open.
|
|
void commit() {
|
|
close();
|
|
if (Depth == 0) {
|
|
assert(PrevDiagnostics == 0);
|
|
Engine.emitTentativeDiagnostics();
|
|
}
|
|
}
|
|
|
|
private:
|
|
void close() {
|
|
assert(IsOpen && "only open transactions may be closed");
|
|
IsOpen = false;
|
|
Engine.TransactionCount--;
|
|
assert(Depth == Engine.TransactionCount &&
|
|
"transactions must be closed LIFO");
|
|
}
|
|
};
|
|
|
|
/// Represents a diagnostic transaction which constructs a compound diagnostic
|
|
/// from any diagnostics emitted inside. A compound diagnostic consists of a
|
|
/// parent error, warning, or remark followed by a variable number of child
|
|
/// notes. The semantics are otherwise the same as a regular
|
|
/// DiagnosticTransaction.
|
|
class CompoundDiagnosticTransaction : public DiagnosticTransaction {
|
|
public:
|
|
explicit CompoundDiagnosticTransaction(DiagnosticEngine &engine)
|
|
: DiagnosticTransaction(engine) {}
|
|
|
|
~CompoundDiagnosticTransaction() {
|
|
if (IsOpen) {
|
|
commit();
|
|
}
|
|
|
|
if (Depth == 0) {
|
|
Engine.TransactionStrings.clear();
|
|
Engine.TransactionAllocator.Reset();
|
|
}
|
|
}
|
|
|
|
void commit() {
|
|
assert(PrevDiagnostics < Engine.TentativeDiagnostics.size() &&
|
|
"CompoundDiagnosticTransaction must contain at least one diag");
|
|
|
|
// The first diagnostic is assumed to be the parent. If this is not an
|
|
// error or warning, we'll assert later when trying to add children.
|
|
Diagnostic &parent = Engine.TentativeDiagnostics[PrevDiagnostics];
|
|
|
|
// Associate the children with the parent.
|
|
for (auto diag =
|
|
Engine.TentativeDiagnostics.begin() + PrevDiagnostics + 1;
|
|
diag != Engine.TentativeDiagnostics.end(); ++diag) {
|
|
diag->setIsChildNote(true);
|
|
parent.addChildNote(std::move(*diag));
|
|
}
|
|
|
|
// Erase the children, they'll be emitted alongside their parent.
|
|
Engine.TentativeDiagnostics.erase(Engine.TentativeDiagnostics.begin() +
|
|
PrevDiagnostics + 1,
|
|
Engine.TentativeDiagnostics.end());
|
|
|
|
DiagnosticTransaction::commit();
|
|
}
|
|
};
|
|
|
|
inline void
|
|
DiagnosticEngine::diagnoseWithNotes(InFlightDiagnostic parentDiag,
|
|
llvm::function_ref<void(void)> builder) {
|
|
CompoundDiagnosticTransaction transaction(*this);
|
|
parentDiag.flush();
|
|
builder();
|
|
}
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|