diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index c83ca9ab101fa..e9a26041b10eb 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -494,6 +494,9 @@ ERROR(error_create_cas, none, "failed to create CAS '%0' (%1)", (StringRef, Stri ERROR(error_invalid_cas_id, none, "invalid CASID '%0' (%1)", (StringRef, StringRef)) ERROR(error_cas, none, "CAS error encountered: %0", (StringRef)) +ERROR(error_failed_cached_diag, none, "failed to serialize cached diagnostics: %0", (StringRef)) +ERROR(error_replay_cached_diag, none, "failed to replay cached diagnostics: %0", (StringRef)) + // Dependency Verifier Diagnostics ERROR(missing_member_dependency,none, "expected " diff --git a/include/swift/Frontend/CachedDiagnostics.h b/include/swift/Frontend/CachedDiagnostics.h new file mode 100644 index 0000000000000..7b796c56b2cd9 --- /dev/null +++ b/include/swift/Frontend/CachedDiagnostics.h @@ -0,0 +1,53 @@ +//===--- CachedDiagnostics.h - Cached Diagnostics ---------------*- 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 defines the CachedDiagnosticConsumer class, which +// caches the diagnostics which can be replayed with other DiagnosticConumers. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_CACHEDDIAGNOSTICS_H +#define SWIFT_CACHEDDIAGNOSTICS_H + +#include "llvm/Support/Error.h" + +namespace swift { + +class CompilerInstance; +class DiagnosticEngine; +class SourceManager; +class FrontendInputsAndOutputs; + +class CachingDiagnosticsProcessor { +public: + CachingDiagnosticsProcessor(CompilerInstance &Instance); + ~CachingDiagnosticsProcessor(); + + /// Start capturing all the diagnostics from DiagnosticsEngine. + void startDiagnosticCapture(); + /// End capturing all the diagnostics from DiagnosticsEngine. + void endDiagnosticCapture(); + + /// Emit serialized diagnostics into output stream. + llvm::Error serializeEmittedDiagnostics(llvm::raw_ostream &os); + + /// Used to replay the previously cached diagnostics, after a cache hit. + llvm::Error replayCachedDiagnostics(llvm::StringRef Buffer); + +private: + class Implementation; + Implementation& Impl; +}; + +} + +#endif diff --git a/include/swift/Frontend/Frontend.h b/include/swift/Frontend/Frontend.h index da83fce1455ff..535d826b2963d 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -30,6 +30,7 @@ #include "swift/Basic/LangOptions.h" #include "swift/Basic/SourceManager.h" #include "swift/ClangImporter/ClangImporter.h" +#include "swift/Frontend/CachedDiagnostics.h" #include "swift/Frontend/DiagnosticVerifier.h" #include "swift/Frontend/FrontendOptions.h" #include "swift/Frontend/ModuleInterfaceSupport.h" @@ -462,6 +463,7 @@ class CompilerInstance { std::unique_ptr Context; std::unique_ptr TheSILTypes; std::unique_ptr DiagVerifier; + std::unique_ptr CDP; /// A cache describing the set of inter-module dependencies that have been queried. /// Null if not present. @@ -548,6 +550,9 @@ class CompilerInstance { Optional getCompilerBaseKey() const { return CompileJobBaseKey; } + CachingDiagnosticsProcessor *getCachingDiagnosticsProcessor() const { + return CDP.get(); + } ASTContext &getASTContext() { return *Context; } const ASTContext &getASTContext() const { return *Context; } @@ -678,6 +683,7 @@ class CompilerInstance { void setupDependencyTrackerIfNeeded(); bool setupCASIfNeeded(ArrayRef Args); void setupOutputBackend(); + void setupCachingDiagnosticsProcessorIfNeeded(); /// \return false if successful, true on error. bool setupDiagnosticVerifierIfNeeded(); diff --git a/lib/DriverTool/swift_cache_tool_main.cpp b/lib/DriverTool/swift_cache_tool_main.cpp index cf2895c116549..86c05d174633d 100644 --- a/lib/DriverTool/swift_cache_tool_main.cpp +++ b/lib/DriverTool/swift_cache_tool_main.cpp @@ -42,7 +42,8 @@ enum class SwiftCacheToolAction { Invalid, PrintBaseKey, PrintOutputKeys, - ValidateOutputs + ValidateOutputs, + RenderDiags }; struct OutputEntry { @@ -134,11 +135,13 @@ class SwiftCacheToolInvocation { .Case("print-base-key", SwiftCacheToolAction::PrintBaseKey) .Case("print-output-keys", SwiftCacheToolAction::PrintOutputKeys) .Case("validate-outputs", SwiftCacheToolAction::ValidateOutputs) + .Case("render-diags", SwiftCacheToolAction::RenderDiags) .Default(SwiftCacheToolAction::Invalid); if (ActionKind == SwiftCacheToolAction::Invalid) { - llvm::errs() << "Invalid option specified for -cache-tool-action: " - << "use print-base-key|print-output-keys|validate-outputs\n"; + llvm::errs() + << "Invalid option specified for -cache-tool-action: " + << "print-base-key|print-output-keys|validate-outputs|render-diags\n"; return 1; } @@ -153,6 +156,8 @@ class SwiftCacheToolInvocation { return printOutputKeys(); case SwiftCacheToolAction::ValidateOutputs: return validateOutputs(); + case SwiftCacheToolAction::RenderDiags: + return renderDiags(); case SwiftCacheToolAction::Invalid: return 0; // No action. Probably just print help. Return. } @@ -202,6 +207,10 @@ class SwiftCacheToolInvocation { return true; } + // Disable diagnostic caching from this fake instance. + if (auto *CDP = Instance.getCachingDiagnosticsProcessor()) + CDP->endDiagnosticCapture(); + return false; } @@ -233,6 +242,7 @@ class SwiftCacheToolInvocation { int printOutputKeys(); int validateOutputs(); + int renderDiags(); }; } // end anonymous namespace @@ -283,6 +293,10 @@ int SwiftCacheToolInvocation::printOutputKeys() { Invocation.getFrontendOptions().InputsAndOutputs.getAllInputs(), addFromInputFile); + // Add diagnostics file. + addOutputKey("", file_types::ID::TY_CachedDiagnostics, + ""); + if (hasError) return 1; @@ -301,6 +315,26 @@ int SwiftCacheToolInvocation::printOutputKeys() { return 0; } +static llvm::Expected +readOutputEntriesFromFile(StringRef Path) { + auto JSONContent = llvm::MemoryBuffer::getFile(Path); + if (!JSONContent) + return llvm::createStringError(JSONContent.getError(), + "failed to read input file"); + + auto JSONValue = llvm::json::parse((*JSONContent)->getBuffer()); + if (!JSONValue) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to parse input file as JSON"); + + auto Keys = JSONValue->getAsArray(); + if (!Keys) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid JSON format for input file"); + + return *Keys; +} + int SwiftCacheToolInvocation::validateOutputs() { auto DB = llvm::cas::createOnDiskUnifiedCASDatabases(CASPath); if (!DB) @@ -310,22 +344,10 @@ int SwiftCacheToolInvocation::validateOutputs() { Instance.getDiags().addConsumer(PDC); auto validateCacheKeysFromFile = [&](const std::string &Path) { - auto JSONContent = llvm::MemoryBuffer::getFile(Path); - if (!JSONContent) { - llvm::errs() << "failed to read " << Path << ": " - << JSONContent.getError().message() << "\n"; - return true; - } - auto JSONValue = llvm::json::parse((*JSONContent)->getBuffer()); - if (!JSONValue) { - llvm::errs() << "failed to parse " << Path << ": " - << toString(JSONValue.takeError()) << "\n"; - return true; - } - - auto Keys = JSONValue->getAsArray(); + auto Keys = readOutputEntriesFromFile(Path); if (!Keys) { - llvm::errs() << "invalid keys format in " << Path << "\n"; + llvm::errs() << "cannot read file " << Path << ": " + << toString(Keys.takeError()) << "\n"; return true; } @@ -350,6 +372,51 @@ int SwiftCacheToolInvocation::validateOutputs() { return llvm::any_of(Inputs, validateCacheKeysFromFile); } +int SwiftCacheToolInvocation::renderDiags() { + if (setupCompiler()) + return 1; + + auto *CDP = Instance.getCachingDiagnosticsProcessor(); + if (!CDP) { + llvm::errs() << "provided commandline doesn't support cached diagnostics\n"; + return 1; + } + + auto renderDiagsFromFile = [&](const std::string &Path) { + auto Keys = readOutputEntriesFromFile(Path); + if (!Keys) { + llvm::errs() << "cannot read file " << Path << ": " + << toString(Keys.takeError()) << "\n"; + return true; + } + + for (const auto& Entry : *Keys) { + if (auto *Obj = Entry.getAsObject()) { + if (auto Kind = Obj->getString("OutputKind")) { + if (*Kind != "cached-diagnostics") + continue; + } + if (auto Key = Obj->getString("CacheKey")) { + if (auto Buffer = loadCachedCompileResultFromCacheKey( + Instance.getObjectStore(), Instance.getActionCache(), + Instance.getDiags(), *Key)) { + if (auto E = CDP->replayCachedDiagnostics(Buffer->getBuffer())) { + llvm::errs() << "failed to replay cache: " + << toString(std::move(E)) << "\n"; + return true; + } + return false; + } + } + } + } + llvm::errs() << "cannot locate cached diagnostics in file\n"; + return true; + }; + + return llvm::any_of(Inputs, renderDiagsFromFile); +} + int swift_cache_tool_main(ArrayRef Args, const char *Argv0, void *MainAddr) { INITIALIZE_LLVM(); diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index cf146d61688ac..c01fdcf7e174b 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -3,6 +3,7 @@ add_swift_host_library(swiftFrontend STATIC ArgsToFrontendInputsConverter.cpp ArgsToFrontendOptionsConverter.cpp ArgsToFrontendOutputsConverter.cpp + CachedDiagnostics.cpp CachingUtils.cpp CompileJobCacheKey.cpp CompilerInvocation.cpp diff --git a/lib/Frontend/CachedDiagnostics.cpp b/lib/Frontend/CachedDiagnostics.cpp new file mode 100644 index 0000000000000..e325b1a3e90a4 --- /dev/null +++ b/lib/Frontend/CachedDiagnostics.cpp @@ -0,0 +1,809 @@ +//===--- CachedDiagnostics.cpp - Cached Diagnostics -----------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 implements the CachedDiagnosticsProcessor class. +// +//===----------------------------------------------------------------------===// + +#include "swift/Frontend/CachedDiagnostics.h" + +#include "swift/AST/DiagnosticConsumer.h" +#include "swift/AST/DiagnosticsFrontend.h" +#include "swift/Basic/SourceManager.h" +#include "swift/Frontend/CachingUtils.h" +#include "swift/Frontend/Frontend.h" +#include "swift/Frontend/FrontendInputsAndOutputs.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/TinyPtrVector.h" +#include "llvm/Support/Compression.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/SMLoc.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +#define DEBUG_TYPE "cached-diags" + +using namespace swift; + +namespace { + +struct SerializedSourceLoc { + unsigned FileID = 0; + unsigned Offset = 0; + + bool operator==(const SerializedSourceLoc &RHS) { + return FileID == RHS.FileID && Offset == RHS.Offset; + } +}; + +struct SerializedCharSourceRange { + SerializedSourceLoc Start; + unsigned ByteLength; +}; + +struct SerializedFixIt { + SerializedCharSourceRange Range; + std::string Text; +}; + +struct SerializedDiagnosticInfo { + static_assert(sizeof(swift::DiagID) == sizeof(uint32_t), "DiagID size"); + static_assert(sizeof(swift::DiagnosticKind) == sizeof(uint8_t), + "DiagKind size"); + uint32_t ID; + SerializedSourceLoc Loc; + uint8_t Kind; + std::string FormatString; + std::string Category; + SerializedSourceLoc BufferIndirectlyCausingDiagnostic; + std::vector ChildDiagnosticInfo; + std::vector EducationalNotePaths; + std::vector Ranges; + std::vector FixIts; + bool IsChildNote; +}; + +struct SerializedFile { + std::string FileName; + SerializedSourceLoc IncludeLoc = SerializedSourceLoc(); + StringRef Content; +}; + +struct SerializedVirtualFile { + std::string FileName; + SerializedCharSourceRange Range; + int LineOffset; +}; + +struct SerializedGeneratedFileInfo { + uint8_t Kind; + unsigned FileID; + SerializedCharSourceRange OriginalRange; + SerializedCharSourceRange GeneratedRange; +}; + +struct DiagnosticSerializer { + DiagnosticSerializer(llvm::IntrusiveRefCntPtr FS) + : SrcMgr(FS) {} + + using ReplayFunc = llvm::function_ref; + + // public APIs for serialization. + void handleDiagnostic(SourceManager &SM, const DiagnosticInfo &Info, + ReplayFunc Fn = nullptr); + llvm::Error serializeEmittedDiagnostics(llvm::raw_ostream &os); + + static llvm::Error emitDiagnosticsFromCached(llvm::StringRef Buffer, + SourceManager &SrcMgr, + DiagnosticEngine &Diags) { + // Create a new DiagnosticSerializer since this cannot be shared with a + // serialization instance. + DiagnosticSerializer DS(SrcMgr.getFileSystem()); + return DS.doEmitFromCached(Buffer, Diags); + } + + SourceManager &getSourceMgr() { return SrcMgr; } + +private: + // Serialization helper + unsigned getFileIDFromBufferID(SourceManager &SM, unsigned Idx); + SerializedSourceLoc convertSourceLoc(SourceManager &SM, const SourceLoc &Loc, + bool AddVirtualFile = true); + SerializedCharSourceRange convertSourceRange(SourceManager &SM, + const CharSourceRange &Range, + bool AddVirtualFile = true); + SerializedFixIt convertFixIt(SourceManager &SM, + const DiagnosticInfo::FixIt &FI); + SerializedDiagnosticInfo convertDiagnosticInfo(SourceManager &SM, + const DiagnosticInfo &Info); + + // Deserialization helper + llvm::Error doEmitFromCached(llvm::StringRef Buffer, DiagnosticEngine &Diags); + llvm::Expected deserializeSourceLoc(const SerializedSourceLoc &); + llvm::Expected + deserializeSourceRange(const SerializedCharSourceRange &); + llvm::Expected + deserializeFixIt(const SerializedFixIt &); + + llvm::Error deserializeDiagnosticInfo(const SerializedDiagnosticInfo &, + ReplayFunc); + + // Deserialize File and return the bufferID in serializing SourceManager. + unsigned deserializeFile(const SerializedFile &File); + llvm::Error deserializeVirtualFile(const SerializedVirtualFile &VF); + llvm::Error deserializeGeneratedFileInfo(const SerializedGeneratedFileInfo &Info); + +public: + std::vector DiagInfos; + std::vector Files; + std::vector VFiles; + std::vector GeneratedFileInfo; + +private: + // Handle FileID. ID 0 is reserved for SMLoc(). + unsigned CurrentFileID = 0; + + // Serializing SourceManager. + SourceManager SrcMgr; + + // Mapping of the FileID between SourceManager from CompilerInstance vs. + // the serialized FileID in cached diagnostics. Lookup tables are + // per-SourceManager to handle diagnostics from all sub-instances which + // uses different SourceManager. + llvm::DenseMap> + FileMapper; + llvm::DenseMap> SeenVFiles; +}; +} + +namespace llvm::yaml { +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedSourceLoc &Loc) { + io.mapRequired("FileID", Loc.FileID); + io.mapRequired("Offset", Loc.Offset); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedCharSourceRange &Range) { + io.mapRequired("Loc", Range.Start); + io.mapRequired("Length", Range.ByteLength); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedFixIt &FixIt) { + io.mapRequired("Range", FixIt.Range); + io.mapRequired("Text", FixIt.Text); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedDiagnosticInfo &Info) { + io.mapRequired("DiagID", Info.ID); + io.mapRequired("Loc", Info.Loc); + io.mapRequired("Kind", Info.Kind); + io.mapRequired("Text", Info.FormatString); + io.mapOptional("Category", Info.Category, ""); + io.mapOptional("BICD", Info.BufferIndirectlyCausingDiagnostic); + io.mapOptional("ChildDiag", Info.ChildDiagnosticInfo); + io.mapOptional("EducationalNotePaths", Info.EducationalNotePaths); + io.mapOptional("Ranges", Info.Ranges); + io.mapOptional("FixIts", Info.FixIts); + io.mapOptional("IsChildNote", Info.IsChildNote, false); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedFile &F) { + io.mapRequired("Name", F.FileName); + io.mapOptional("IncludeLoc", F.IncludeLoc, SerializedSourceLoc()); + io.mapOptional("Content", F.Content, StringRef()); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedVirtualFile &F) { + io.mapRequired("Name", F.FileName); + io.mapRequired("Range", F.Range); + io.mapOptional("LineOffset", F.LineOffset, 0); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, SerializedGeneratedFileInfo &Info) { + io.mapRequired("Kind", Info.Kind); + io.mapRequired("FileID", Info.FileID); + io.mapRequired("OriginalRange", Info.OriginalRange); + io.mapRequired("GeneratedRange", Info.GeneratedRange); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &io, DiagnosticSerializer &S) { + io.mapRequired("Files", S.Files); + io.mapOptional("VirtualFiles", S.VFiles); + io.mapRequired("Diagnostics", S.DiagInfos); + io.mapOptional("GeneratedFileInfo", S.GeneratedFileInfo); + } +}; + + +} // namespace llvm::yaml + +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedCharSourceRange) +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedFixIt) +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedDiagnosticInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedFile) +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedVirtualFile) +LLVM_YAML_IS_SEQUENCE_VECTOR(SerializedGeneratedFileInfo) + +void DiagnosticSerializer::handleDiagnostic(SourceManager &SM, + const DiagnosticInfo &Info, + ReplayFunc Fn) { + DiagInfos.emplace_back(convertDiagnosticInfo(SM, Info)); + if (Fn) + cantFail(deserializeDiagnosticInfo(DiagInfos.back(), Fn)); +} + +unsigned DiagnosticSerializer::getFileIDFromBufferID(SourceManager &SM, + unsigned Idx) { + auto &Buf = SM.getLLVMSourceMgr().getBufferInfo(Idx); + auto Filename = Buf.Buffer->getBufferIdentifier(); + bool IsFSBacked = SM.getFileSystem()->exists(Filename); + + // See if the file is already constructed. + auto &Allocated = FileMapper[&SM]; + auto ID = Allocated.find(Idx); + if (ID != Allocated.end()) + return ID->second; + + // Construct and add to files. If there is an IncludeLoc, the file from + // IncludeLoc is added before current file. + assert(CurrentFileID == Files.size() && "File index mismatch"); + StringRef FileContent = IsFSBacked ? StringRef() : Buf.Buffer->getBuffer(); + SerializedFile File = {Filename.str(), + convertSourceLoc(SM, SourceLoc(Buf.IncludeLoc)), + FileContent}; + // Add file to serializing source manager. + FileMapper[&SrcMgr].insert({CurrentFileID, deserializeFile(File)}); + + Files.emplace_back(std::move(File)); + Allocated.insert({Idx, ++CurrentFileID}); + + + auto Info = SM.getGeneratedSourceInfo(Idx); + auto convertGeneratedFileInfo = + [&](const GeneratedSourceInfo &Info) -> SerializedGeneratedFileInfo { + return {(uint8_t)Info.kind, CurrentFileID, + convertSourceRange(SM, Info.originalSourceRange), + convertSourceRange(SM, Info.generatedSourceRange)}; + }; + if (Info) { + auto GI = convertGeneratedFileInfo(*Info); + // Add generated file info to source manager. + cantFail(deserializeGeneratedFileInfo(GI)); + GeneratedFileInfo.emplace_back(std::move(GI)); + } + + return CurrentFileID; +} + +SerializedSourceLoc +DiagnosticSerializer::convertSourceLoc(SourceManager &SM, const SourceLoc &Loc, + bool AddVirtualFile) { + if (Loc == SourceLoc()) + return SerializedSourceLoc(); + + // This is locked with ABI for llvm::SMLoc and llvm::SourceMgr. + assert(SM.isOwning(Loc) && "SourceLoc is not owned by SourceManager"); + + // Check VirtualFile. If the SourceLoc is from a virtual file, create a + // 1 byte virtual file that is just enough to map the diagnostic. + // Don't try to remap the entire region when the diagnostics was handled since + // virtual file region can change if the diagnostics are from parser. + // This assumes the same SourceLoc cannot be mapped to different virtual file + // during the compilation. + if (AddVirtualFile) { + auto convertVirtualFile = [&](const std::string &Name, SourceLoc Start, + int LineOffset) -> SerializedVirtualFile { + CharSourceRange Range(Start, 1); + return {Name, convertSourceRange(SM, Range, /*AddVirtualFile=*/false), + LineOffset}; + }; + if (auto *VF = SM.getVirtualFile(Loc)) { + const char* VFStart = (const char*) Loc.getOpaquePointerValue(); + if (!SeenVFiles[&SM].count(VFStart)) { + auto SVF = convertVirtualFile(VF->Name, Loc, VF->LineOffset); + cantFail(deserializeVirtualFile(SVF)); + VFiles.emplace_back(std::move(SVF)); + SeenVFiles[&SM].insert(VFStart); + } + } + } + unsigned BufID = SM.findBufferContainingLoc(Loc); + unsigned FileID = getFileIDFromBufferID(SM, BufID); + auto &Info = SM.getLLVMSourceMgr().getBufferInfo(BufID); + unsigned Offset = + (const char *)Loc.getOpaquePointerValue() - Info.Buffer->getBufferStart(); + + return {FileID, Offset}; +} + +SerializedCharSourceRange DiagnosticSerializer::convertSourceRange( + SourceManager &SM, const CharSourceRange &Range, bool AddVirtualFile) { + return {convertSourceLoc(SM, Range.getStart(), AddVirtualFile), + Range.isValid() ? Range.getByteLength() : 0}; +} + +SerializedFixIt +DiagnosticSerializer::convertFixIt(SourceManager &SM, + const DiagnosticInfo::FixIt &FI) { + return {convertSourceRange(SM, FI.getRange()), FI.getText().str()}; +} + +SerializedDiagnosticInfo +DiagnosticSerializer::convertDiagnosticInfo(SourceManager &SM, + const DiagnosticInfo &Info) { + llvm::SmallString<256> Text; + { + llvm::SmallString<256> Formatted; + llvm::raw_svector_ostream OS(Formatted); + DiagnosticEngine::formatDiagnosticText(OS, Info.FormatString, + Info.FormatArgs); + + // If formatted diagnostic has "%" in it, it needs to be rewrite to "%%". + StringRef InText(Formatted); + llvm::raw_svector_ostream Out(Text); + auto Percent = InText.split('%'); + Out << Percent.first; + while (!Percent.second.empty()) { + Out << "%%"; + Percent = Percent.second.split('%'); + Out << Percent.first; + } + } + + auto convertDiagnosticInfoArray = [&](ArrayRef Infos) { + std::vector Serialized; + Serialized.reserve(Infos.size()); + llvm::for_each(Infos, [&](DiagnosticInfo *Info) { + return Serialized.emplace_back(convertDiagnosticInfo(SM, *Info)); + }); + return Serialized; + }; + + auto convertSourceRangeArray = [&](ArrayRef Ranges) { + std::vector Serialized; + Serialized.reserve(Ranges.size()); + llvm::for_each(Ranges, [&](const CharSourceRange &Range) { + return Serialized.emplace_back(convertSourceRange(SM, Range)); + }); + return Serialized; + }; + + auto convertFixItArray = [&](ArrayRef FixIts) { + std::vector Serialized; + Serialized.reserve(FixIts.size()); + llvm::for_each(FixIts, [&](const DiagnosticInfo::FixIt &FI) { + return Serialized.emplace_back(convertFixIt(SM, FI)); + }); + return Serialized; + }; + + return {(uint32_t)Info.ID, + convertSourceLoc(SM, Info.Loc), + (uint8_t)Info.Kind, + std::string(Text.data(), Text.size()), + Info.Category.str(), + convertSourceLoc(SM, Info.BufferIndirectlyCausingDiagnostic), + convertDiagnosticInfoArray(Info.ChildDiagnosticInfo), + std::vector(Info.EducationalNotePaths.begin(), + Info.EducationalNotePaths.end()), + convertSourceRangeArray(Info.Ranges), + convertFixItArray(Info.FixIts), + Info.IsChildNote}; +} + +static llvm::Error createDeserializationError(StringRef Msg) { + return llvm::createStringError(std::errc::protocol_error, Msg.str().c_str()); +} + +llvm::Expected +DiagnosticSerializer::deserializeSourceLoc(const SerializedSourceLoc &Loc) { + if (Loc.FileID == 0) + return SourceLoc(); + + auto BufID = FileMapper[&SrcMgr].find(Loc.FileID - 1); + if (BufID == FileMapper[&SrcMgr].end()) + return createDeserializationError("File doesn't exist in SourceManager"); + auto &Info = SrcMgr.getLLVMSourceMgr().getBufferInfo(BufID->second); + const char *Buffer = Info.Buffer->getBufferStart(); + llvm::SMLoc SL = llvm::SMLoc::getFromPointer(Buffer + Loc.Offset); + return SourceLoc(SL); +} + +llvm::Expected DiagnosticSerializer::deserializeSourceRange( + const SerializedCharSourceRange &Range) { + auto Start = deserializeSourceLoc(Range.Start); + if (!Start) + return Start.takeError(); + + return CharSourceRange(*Start, Range.ByteLength); +} + +llvm::Expected +DiagnosticSerializer::deserializeFixIt(const SerializedFixIt &FI) { + auto Range = deserializeSourceRange(FI.Range); + if (!Range) + return Range.takeError(); + + return DiagnosticInfo::FixIt(*Range, FI.Text, {}); +} + +unsigned DiagnosticSerializer::deserializeFile(const SerializedFile &File) { + assert(File.IncludeLoc.FileID == 0 && "IncludeLoc not supported yet"); + return File.Content.empty() + ? SrcMgr.getExternalSourceBufferID(File.FileName) + : SrcMgr.addNewSourceBuffer(llvm::MemoryBuffer::getMemBufferCopy( + File.Content, File.FileName)); +} + +llvm::Error +DiagnosticSerializer::deserializeVirtualFile(const SerializedVirtualFile &VF) { + auto Range = deserializeSourceRange(VF.Range); + if (!Range) + return Range.takeError(); + unsigned Length = (const char *)Range->getEnd().getOpaquePointerValue() - + (const char *)Range->getStart().getOpaquePointerValue(); + SrcMgr.createVirtualFile(Range->getStart(), VF.FileName, VF.LineOffset, + Length); + return llvm::Error::success(); +} + +llvm::Error DiagnosticSerializer::deserializeGeneratedFileInfo( + const SerializedGeneratedFileInfo &GI) { + auto ID = FileMapper[&SrcMgr].find(GI.FileID - 1); + if (ID == FileMapper[&SrcMgr].end()) + return createDeserializationError( + "BufferID for GeneratedSourceInfo not found"); + GeneratedSourceInfo Info; + Info.kind = (GeneratedSourceInfo::Kind)GI.Kind; + auto OriginalRange = deserializeSourceRange(GI.OriginalRange); + if (!OriginalRange) + return OriginalRange.takeError(); + Info.originalSourceRange = *OriginalRange; + auto GeneratedRange = deserializeSourceRange(GI.GeneratedRange); + if (!GeneratedRange) + return GeneratedRange.takeError(); + Info.generatedSourceRange = *GeneratedRange; + SrcMgr.setGeneratedSourceInfo(ID->second, Info); + return llvm::Error::success(); +} + +llvm::Error DiagnosticSerializer::deserializeDiagnosticInfo( + const SerializedDiagnosticInfo &Info, ReplayFunc callback) { + DiagID ID = (DiagID)Info.ID; + auto Loc = deserializeSourceLoc(Info.Loc); + if (!Loc) + return Loc.takeError(); + DiagnosticKind Kind = (DiagnosticKind)Info.Kind; + auto BICD = deserializeSourceLoc(Info.BufferIndirectlyCausingDiagnostic); + if (!BICD) + return BICD.takeError(); + SmallVector ChildDiag; + for (auto &CD : Info.ChildDiagnosticInfo) { + auto E = deserializeDiagnosticInfo(CD, [&](const DiagnosticInfo &Info) { + ChildDiag.emplace_back(Info); + return llvm::Error::success(); + }); + if (E) + return E; + } + llvm::TinyPtrVector ChildDiagPtrs; + llvm::for_each(ChildDiag, [&ChildDiagPtrs](DiagnosticInfo &I) { + ChildDiagPtrs.push_back(&I); + }); + SmallVector Ranges; + for (auto &R : Info.Ranges) { + auto Range = deserializeSourceRange(R); + if (!Range) + return Range.takeError(); + Ranges.emplace_back(*Range); + } + SmallVector FixIts; + for (auto &F : Info.FixIts) { + auto FixIt = deserializeFixIt(F); + if (!FixIt) + return FixIt.takeError(); + FixIts.emplace_back(*FixIt); + } + + DiagnosticInfo DeserializedInfo{ID, + *Loc, + Kind, + Info.FormatString, + {}, + Info.Category, + *BICD, + ChildDiagPtrs, + Ranges, + FixIts, + Info.IsChildNote}; + DeserializedInfo.EducationalNotePaths = Info.EducationalNotePaths; + return callback(DeserializedInfo); +} + +llvm::Error +DiagnosticSerializer::serializeEmittedDiagnostics(llvm::raw_ostream &os) { + llvm::yaml::Output yout(os); + yout << *this; + return llvm::Error::success(); +} + +llvm::Error DiagnosticSerializer::doEmitFromCached(llvm::StringRef Buffer, + DiagnosticEngine &Diags) { + llvm::yaml::Input yin(Buffer); + yin >> *this; + + if (yin.error()) + return llvm::errorCodeToError(yin.error()); + + // Populate SourceManager with Files. + unsigned ID = 0; + for (auto &File : Files) { + assert(File.IncludeLoc.FileID == 0 && "IncludeLoc not supported yet"); + unsigned Idx = deserializeFile(File); + FileMapper[&SrcMgr].insert({ID++, Idx}); + } + + for (auto &VF : VFiles) { + if (auto E = deserializeVirtualFile(VF)) + return E; + } + + for (auto &GI : GeneratedFileInfo) { + if (auto E = deserializeGeneratedFileInfo(GI)) + return E; + } + + for (auto &Info : DiagInfos) { + auto E = deserializeDiagnosticInfo(Info, [&](const DiagnosticInfo &Info) { + for (auto *Diag : Diags.getConsumers()) + Diag->handleDiagnostic(SrcMgr, Info); + return llvm::Error::success(); + }); + if (E) + return E; + } + return llvm::Error::success(); +} + +class CachingDiagnosticsProcessor::Implementation + : public swift::DiagnosticConsumer { +public: + Implementation(CompilerInstance &Instance) + : InstanceSourceMgr(Instance.getSourceMgr()), + InAndOut( + Instance.getInvocation().getFrontendOptions().InputsAndOutputs), + Diags(Instance.getDiags()) {} + ~Implementation() {} + + void startDiagnosticCapture() { + assert(!IsCapturing && "Already started capturing"); + OrigConsumers = Diags.takeConsumers(); + Diags.addConsumer(*this); + IsCapturing = true; + } + + void endDiagnosticCapture() { + assert(IsCapturing && "Did not start capturing"); + assert(Diags.getConsumers().size() == 1 && "Overlapping capture"); + Diags.removeConsumer(*this); + llvm::for_each(OrigConsumers, [&](DiagnosticConsumer *DC) { + Diags.addConsumer(*DC); + }); + OrigConsumers.clear(); + IsCapturing = false; + } + + llvm::Error replayCachedDiagnostics(llvm::StringRef Buffer) { + return DiagnosticSerializer::emitDiagnosticsFromCached( + Buffer, getDiagnosticSourceMgr(), Diags); + } + + void handleDiagnostic(SourceManager &SM, + const DiagnosticInfo &Info) override { + auto &Serializer = getSerializer(); + assert(SM.getFileSystem() == Serializer.getSourceMgr().getFileSystem() && + "Caching for a different file system"); + Serializer.handleDiagnostic(SM, Info, [&](const DiagnosticInfo &Info) { + for (auto *Diag : OrigConsumers) + Diag->handleDiagnostic(getDiagnosticSourceMgr(), Info); + return llvm::Error::success(); + }); + } + + bool finishProcessing() override { + // Finish all the consumers that are being captured. + for (auto *Diag : OrigConsumers) + Diag->finishProcessing(); + + endDiagnosticCapture(); + llvm::SmallString<256> Text; + llvm::raw_svector_ostream OS(Text); + if (auto Err = serializeEmittedDiagnostics(OS)) { + Diags.diagnose(SourceLoc(), diag::error_failed_cached_diag, + toString(std::move(Err))); + return true; + } + return serializedOutputCallback(OS.str()); + } + + llvm::Error serializeEmittedDiagnostics(llvm::raw_ostream &os) { + assert(!IsCapturing && "End capture before emitting"); + return getSerializer().serializeEmittedDiagnostics(os); + } + +private: + SourceManager &getDiagnosticSourceMgr() { + return getSerializer().getSourceMgr(); + } + + DiagnosticSerializer &getSerializer() { + // If the DiagnosticSerializer is not setup, create it. It cannot + // be created on the creation of CachingDiagnosticsProcessor because the + // Job can overwrite the FileSystem in CompilerInstance. Diagnostics + // SourceManager is created with the filesystem in source manager in + // compiler instance on the first diagnostics and assert if the underlying + // file system changes on later diagnostics. + if (!Serializer) { + Serializer.reset( + new DiagnosticSerializer(InstanceSourceMgr.getFileSystem())); + auto &SM = Serializer->getSourceMgr(); + // Extract all the input file names so they can be added to the source + // manager when replaying the diagnostics. All input files are needed even + // they don't contain diagnostics because FileSpecificDiagConsumer need + // has references to input files to find subconsumer. + auto addInputToSourceMgr = [&](const InputFile &Input) { + if (Input.getFileName() != "-") + SM.getExternalSourceBufferID(Input.getFileName()); + return false; + }; + InAndOut.forEachInputProducingSupplementaryOutput(addInputToSourceMgr); + InAndOut.forEachNonPrimaryInput(addInputToSourceMgr); + } + + return *Serializer; + } + +private: + friend CachingDiagnosticsProcessor; + std::vector OrigConsumers; + + // Owning SourceManager for replaying diagnostics. SourceManager needs to + // be alive until all consumers finishProcessing() and user needs to keep + // Processor/Serializer alive until then. + std::unique_ptr Serializer; + + SourceManager &InstanceSourceMgr; + const FrontendInputsAndOutputs &InAndOut; + DiagnosticEngine &Diags; + + llvm::unique_function serializedOutputCallback; + + bool IsCapturing = false; +}; + +CachingDiagnosticsProcessor::CachingDiagnosticsProcessor( + CompilerInstance &Instance) + : Impl(*new Implementation(Instance)) { + Impl.serializedOutputCallback = [&](StringRef Output) { + LLVM_DEBUG(llvm::dbgs() << Output << "\n";); + if (!Instance.getInvocation().getFrontendOptions().EnableCAS) + return false; + + // compress the YAML file. + llvm::SmallVector Compression; + if (llvm::compression::zstd::isAvailable()) + llvm::compression::zstd::compress(arrayRefFromStringRef(Output), + Compression); + else if (llvm::compression::zlib::isAvailable()) + llvm::compression::zlib::compress(arrayRefFromStringRef(Output), + Compression); + + // Write the uncompressed size in the end. + if (!Compression.empty()) { + llvm::raw_svector_ostream BufOS((SmallVectorImpl &)Compression); + llvm::support::endian::Writer Writer(BufOS, llvm::support::little); + Writer.write(uint32_t(Output.size())); + } + + StringRef Content = Compression.empty() ? Output : toStringRef(Compression); + // Store CachedDiagnostics in the CAS/Cache. There is no real associated + // inputs. + auto Err = storeCachedCompilerOutput( + Instance.getObjectStore(), Instance.getActionCache(), + "", Content, *Instance.getCompilerBaseKey(), + "", file_types::ID::TY_CachedDiagnostics); + + if (Err) { + Instance.getDiags().diagnose(SourceLoc(), diag::error_cas, + toString(std::move(Err))); + return true; + } + + return false; + }; +} + +CachingDiagnosticsProcessor::~CachingDiagnosticsProcessor() { delete &Impl; } + +void CachingDiagnosticsProcessor::startDiagnosticCapture() { + Impl.startDiagnosticCapture(); +} + +void CachingDiagnosticsProcessor::endDiagnosticCapture() { + Impl.endDiagnosticCapture(); +} + +llvm::Error CachingDiagnosticsProcessor::serializeEmittedDiagnostics( + llvm::raw_ostream &os) { + return Impl.serializeEmittedDiagnostics(os); +} + +llvm::Error +CachingDiagnosticsProcessor::replayCachedDiagnostics(llvm::StringRef Buffer) { + SmallVector Uncompressed; + if (llvm::compression::zstd::isAvailable() || + llvm::compression::zlib::isAvailable()) { + if (Buffer.size() < sizeof(uint32_t)) + return llvm::errorCodeToError( + std::make_error_code(std::errc::message_size)); + + uint32_t UncompressedSize = + llvm::support::endian::read( + Buffer.data() + Buffer.size() - sizeof(uint32_t)); + + StringRef CompressedData = Buffer.drop_back(sizeof(uint32_t)); + if (llvm::compression::zstd::isAvailable()) { + if (auto E = llvm::compression::zstd::decompress( + arrayRefFromStringRef(CompressedData), Uncompressed, + UncompressedSize)) + return E; + } else if (llvm::compression::zlib::isAvailable()) { + if (auto E = llvm::compression::zlib::decompress( + arrayRefFromStringRef(CompressedData), Uncompressed, + UncompressedSize)) + return E; + } + } + + StringRef InputBuffer = + Uncompressed.empty() ? Buffer : toStringRef(Uncompressed); + + return Impl.replayCachedDiagnostics(InputBuffer); +} diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index c0d2f4f16b2f5..1d0fd86d191bc 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -467,6 +467,15 @@ void CompilerInstance::setupOutputBackend() { } } +void CompilerInstance::setupCachingDiagnosticsProcessorIfNeeded() { + if (!supportCaching()) + return; + + // Only setup if using CAS. + CDP = std::make_unique(*this); + CDP->startDiagnosticCapture(); +} + bool CompilerInstance::setup(const CompilerInvocation &Invoke, std::string &Error, ArrayRef Args) { Invocation = Invoke; @@ -507,6 +516,10 @@ bool CompilerInstance::setup(const CompilerInvocation &Invoke, return true; } + // Setup caching diagnostics processor. It should be setup after all other + // DiagConsumers are added. + setupCachingDiagnosticsProcessorIfNeeded(); + // If we expect an implicit stdlib import, load in the standard library. If we // either fail to find it or encounter an error while loading it, bail early. Continuing will at best // trigger a bunch of other errors due to the stdlib being missing, or at diff --git a/test/CAS/Inputs/objc.h b/test/CAS/Inputs/objc.h index ef83a895f66fe..7a9c90b43215c 100644 --- a/test/CAS/Inputs/objc.h +++ b/test/CAS/Inputs/objc.h @@ -1 +1,3 @@ int test(int); + +#warning warning in bridging header diff --git a/test/CAS/cache_key_compute.swift b/test/CAS/cache_key_compute.swift index e039b5fa110f3..e2ff1e13d04c4 100644 --- a/test/CAS/cache_key_compute.swift +++ b/test/CAS/cache_key_compute.swift @@ -49,3 +49,8 @@ // CHECK-NEXT: "OutputKind": "tbd" // CHECK-NEXT: "Input" // CHECK-NEXT: "CacheKey" + +// CHECK: +// CHECK-NEXT: "OutputKind": "cached-diagnostics" +// CHECK-NEXT: "Input": "" +// CHECK-NEXT: "CacheKey" diff --git a/test/CAS/cached_diagnostics.swift b/test/CAS/cached_diagnostics.swift new file mode 100644 index 0000000000000..19cc2c1d0aaf9 --- /dev/null +++ b/test/CAS/cached_diagnostics.swift @@ -0,0 +1,21 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-frontend -c -enable-cas -cas-path %t/cas -allow-unstable-cache-key-for-testing %s \ +// RUN: -import-objc-header %S/Inputs/objc.h -emit-module -emit-module-path %t/test.swiftmodule 2>&1 | %FileCheck %s +// RUN: %cache-tool -cas-path %t/cas -cache-tool-action print-output-keys -- %target-swift-frontend -c -enable-cas -cas-path %t/cas -allow-unstable-cache-key-for-testing %s \ +// RUN: -import-objc-header %S/Inputs/objc.h -emit-module -emit-module-path %t/test.swiftmodule > %t/cache_key.json +// RUN: %cache-tool -cas-path %t/cas -cache-tool-action render-diags %t/cache_key.json -- %target-swift-frontend -c -enable-cas -cas-path %t/cas -allow-unstable-cache-key-for-testing %s \ +// RUN: -import-objc-header %S/Inputs/objc.h -emit-module -emit-module-path %t/test.swiftmodule 2>&1 | %FileCheck %s + +#warning("this is a warning") // expected-warning {{this is a warning}} + +// CHECK: warning: warning in bridging header +// CHECK: warning: this is a warning + +/// Check other DiagnosticConsumers. +// RUN: %target-swift-frontend -c -enable-cas -cas-path %t/cas -allow-unstable-cache-key-for-testing %s \ +// RUN: -typecheck -serialize-diagnostics -serialize-diagnostics-path %t/test.diag -verify +// RUN: %FileCheck %s -check-prefix CHECK-SERIALIZED <%t/test.diag + +// Verify the serialized diags have the right magic at the top. +// CHECK-SERIALIZED: DIA diff --git a/test/CAS/educational-notes.swift b/test/CAS/educational-notes.swift new file mode 100644 index 0000000000000..f38a7133cb19b --- /dev/null +++ b/test/CAS/educational-notes.swift @@ -0,0 +1,86 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -color-diagnostics -diagnostic-style=llvm -print-educational-notes -diagnostic-documentation-path %S/../diagnostics/test-docs/ \ +// RUN: -emit-module -emit-module-path %t/test.module -allow-unstable-cache-key-for-testing %s 2>&1 | %FileCheck %s --match-full-lines --strict-whitespace +// RUN: not %target-swift-frontend -no-color-diagnostics -print-educational-notes -diagnostic-documentation-path %S/../diagnostics/test-docs/ \ +// RUN: -emit-module -emit-module-path %t/test.module -allow-unstable-cache-key-for-testing %s 2>&1 | %FileCheck %s --match-full-lines --strict-whitespace --check-prefix=NO-COLOR + +// A diagnostic with no educational notes +let x = 1 + +// CHECK:{{.*}}[0merror: expected expression after operator +// CHECK-NOT: {{-+$}} + +// NO-COLOR:{{.*}}error: expected expression after operator +// NO-COLOR-NOT: {{-+$}} + +// A diagnostic with an educational note using supported markdown features +extension (Int, Int) {} +// CHECK:{{.*}}[0merror: non-nominal type '(Int, Int)' cannot be extended +// CHECK-NEXT:extension (Int, Int) {} +// CHECK-NEXT:^ ~~~~~~~~~~ +// CHECK-NEXT:Nominal Types +// CHECK-NEXT:-------------- +// CHECK-EMPTY: +// CHECK-NEXT:Nominal types documentation content. This is a paragraph +// CHECK-EMPTY: +// CHECK-NEXT: blockquote +// CHECK-NEXT: {{$}} +// CHECK-NEXT: - item 1 +// CHECK-NEXT: - item 2 +// CHECK-NEXT: - item 3 +// CHECK-NEXT: {{$}} +// CHECK-NEXT: let x = 42 +// CHECK-NEXT: if x > 0 { +// CHECK-NEXT: print("positive") +// CHECK-NEXT: } +// CHECK-NEXT: {{$}} +// CHECK-NEXT:Type 'MyClass' +// CHECK-EMPTY: +// CHECK-NEXT:[Swift](swift.org) +// CHECK-EMPTY: +// CHECK-NEXT:bold italics +// CHECK-NEXT:-------------- +// CHECK-NEXT:Header 1 +// CHECK-NEXT:Header 3 + +// NO-COLOR:{{.*}}error: non-nominal type '(Int, Int)' cannot be extended +// NO-COLOR-NEXT:extension (Int, Int) {} +// NO-COLOR-NEXT:^ ~~~~~~~~~~ +// NO-COLOR-NEXT:Nominal Types +// NO-COLOR-NEXT:-------------- +// NO-COLOR-EMPTY: +// NO-COLOR-NEXT:Nominal types documentation content. This is a paragraph +// NO-COLOR-EMPTY: +// NO-COLOR-NEXT: blockquote +// NO-COLOR-NEXT: {{$}} +// NO-COLOR-NEXT: - item 1 +// NO-COLOR-NEXT: - item 2 +// NO-COLOR-NEXT: - item 3 +// NO-COLOR-NEXT: {{$}} +// NO-COLOR-NEXT: let x = 42 +// NO-COLOR-NEXT: if x > 0 { +// NO-COLOR-NEXT: print("positive") +// NO-COLOR-NEXT: } +// NO-COLOR-NEXT: {{$}} +// NO-COLOR-NEXT:Type 'MyClass' +// NO-COLOR-EMPTY: +// NO-COLOR-NEXT:[Swift](swift.org) +// NO-COLOR-EMPTY: +// NO-COLOR-NEXT:bold italics +// NO-COLOR-NEXT:-------------- +// NO-COLOR-NEXT:Header 1 +// NO-COLOR-NEXT:Header 3 + +// CHECK-DESCRIPTIVE: educational-notes.swift +// CHECK-DESCRIPTIVE-NEXT: | // A diagnostic with an educational note +// CHECK-DESCRIPTIVE-NEXT: | extension (Int, Int) {} +// CHECK-DESCRIPTIVE-NEXT: | ^ error: expected expression after operator +// CHECK-DESCRIPTIVE-NEXT: | + +// CHECK-DESCRIPTIVE: educational-notes.swift +// CHECK-DESCRIPTIVE-NEXT: | // A diagnostic with an educational note +// CHECK-DESCRIPTIVE-NEXT: | extension (Int, Int) {} +// CHECK-DESCRIPTIVE-NEXT: | ~~~~~~~~~~ +// CHECK-DESCRIPTIVE-NEXT: | ^ error: non-nominal type '(Int, Int)' cannot be extended +// CHECK-DESCRIPTIVE-NEXT: | +// CHECK-DESCRIPTIVE-NEXT: Nominal Types +// CHECK-DESCRIPTIVE-NEXT: ------------- diff --git a/test/CAS/loc-directive-diagnostics.swift b/test/CAS/loc-directive-diagnostics.swift new file mode 100644 index 0000000000000..98b80cacc645b --- /dev/null +++ b/test/CAS/loc-directive-diagnostics.swift @@ -0,0 +1,15 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -emit-module -emit-module-path %t/test.module \ +// RUN: -enable-cas -cas-path %t/cas -allow-unstable-cache-key-for-testing %s 2>&1 | %FileCheck %s +// RUN: %cache-tool -cas-path %t/cas -cache-tool-action print-output-keys -- \ +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/test.module -enable-cas -cas-path %t/cas \ +// RUN: -allow-unstable-cache-key-for-testing %s > %t/cache_key.json +// RUN: %cache-tool -cas-path %t/cas -cache-tool-action render-diags %t/cache_key.json -- \ +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/test.module -enable-cas -cas-path %t/cas \ +// RUN: -allow-unstable-cache-key-for-testing %s 2>&1 | %FileCheck %s + +#sourceLocation(file: "anything.swift", line: 1) +func 1() {} +#sourceLocation() + +// CHECK: anything.swift:1:6: error: function name