| //===--- 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 |