Repository: carbon-language/carbon-lang
File: toolchain/check/import_cpp.cpp (C++)

Code before:
      case clang::DiagnosticsEngine::Warning:
      case clang::DiagnosticsEngine::Error:
      case clang::DiagnosticsEngine::Fatal: {
        CARBON_DIAGNOSTIC(CppInteropDriverWarning, Warning, "{0}", std::string);
        CARBON_DIAGNOSTIC(CppInteropDriverError, Error, "{0}", std::string);
        emitter_->Emit(diag_level == clang::DiagnosticsEngine::Warning
                           ? CppInteropDriverWarning
                           : CppInteropDriverError,
                       message.str().str());
        break;
      }
    }
  }

 private:
  // Diagnostic emitter. Note that driver diagnostics don't have meaningful
  // locations attached.
  Diagnostics::NoLocEmitter* emitter_;
};

}  // namespace

auto BuildClangInvocationImpl(
    Diagnostics::NoLocEmitter& emitter,
    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
    llvm::ArrayRef<std::string> clang_path_and_args)
    -> std::unique_ptr<clang::CompilerInvocation> {
  CarbonClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);

  // The clang driver inconveniently wants an array of `const char*`, so convert
  // the arguments.
  llvm::SmallVector<const char*> driver_args(llvm::map_range(
      clang_path_and_args, [](const std::string& str) { return str.c_str(); }));

  // Add our include file name as the input file, and force it to be interpreted
  // as C++.
  driver_args.push_back("-x");
  driver_args.push_back("c++");
  driver_args.push_back(IncludesFileName);

  // Build a diagnostics engine. Note that we don't have any diagnostic options
  // yet; they're produced by running the driver.
  clang::DiagnosticOptions driver_diag_opts;
  llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
      clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
                                                 &diagnostics_consumer,
                                                 /*ShouldOwnClient=*/false));

  // Ask the driver to process the arguments and build a corresponding clang
  // frontend invocation.
  return clang::createInvocation(driver_args,

Review comment (refactor):
It seems pretty awkward to leave this in `import_cpp.cpp`, and in `check` really, especially as there is only *one* invocation used for a given compile...

AFAICT, there aren't many dependencies on being here unless I'm missing something? (Sorry if so and I just missed it...). Given that, WDYT about moving this into a separate component that both the driver and the language server can use? I'm thinking of a library in `base` like `clang_interop_helpers.h` or something similar? That would then make sense to expose both an API to build an interop-oriented compiler invocation and just get the interop-oriented driver arguments (long term)?

I think that would also nudge us towards some improvements such as using a more generic name than `IncludesFileName`, maybe `InteropFileName`?

Code after:
                                   .import_ir_inst_id = clang_import_ir_inst_id,
                                   .message = message.str().str()});
      return;
    }

    RawStringOstream diagnostics_stream;
    clang::TextDiagnostic text_diagnostic(diagnostics_stream,
                                          invocation_->getLangOpts(),
                                          invocation_->getDiagnosticOpts());
    text_diagnostic.emitDiagnostic(
        clang::FullSourceLoc(info.getLocation(), info.getSourceManager()),
        diag_level, message, info.getRanges(), info.getFixItHints());

    std::string diagnostics_str = diagnostics_stream.TakeStr();

    diagnostic_infos_.push_back({.level = diag_level,
                                 .import_ir_inst_id = clang_import_ir_inst_id,
                                 .message = diagnostics_str});
  }

  // Outputs Carbon diagnostics based on the collected Clang diagnostics. Must
  // be called after the AST is set in the context.
  auto EmitDiagnostics() -> void {
    for (const ClangDiagnosticInfo& info : diagnostic_infos_) {
      switch (info.level) {
        case clang::DiagnosticsEngine::Ignored:
        case clang::DiagnosticsEngine::Note:
        case clang::DiagnosticsEngine::Remark: {
          context_->TODO(
              SemIR::LocId(info.import_ir_inst_id),
              llvm::formatv(
                  "Unsupported: C++ diagnostic level for diagnostic\n{0}",
                  info.message));
          break;
        }
        case clang::DiagnosticsEngine::Warning:
        case clang::DiagnosticsEngine::Error:
        case clang::DiagnosticsEngine::Fatal: {
          CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "{0}",
                            std::string);
          CARBON_DIAGNOSTIC(CppInteropParseError, Error, "{0}", std::string);
          context_->emitter().Emit(
              SemIR::LocId(info.import_ir_inst_id),
              info.level == clang::DiagnosticsEngine::Warning
                  ? CppInteropParseWarning
                  : CppInteropParseError,
              info.message);
          break;
        }
      }
    }