Move SourceFileTypeSet to Target

Have the target track which types of source files it contains, and check
that only one type exists. Prevents recompution in a few places, but
also allows for checking what language a target should be compiled
under.

Note that it will error out if complilation-incompatible source types
are used (e.g. having C and C++ in the same target is fine, but having C
and Rust in the same target isn't).

Change-Id: I5afb038e4cfa7da79c9f264cee79a8c652797dc3
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4880
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc
index f1e4ea4..1f568b4 100644
--- a/tools/gn/binary_target_generator.cc
+++ b/tools/gn/binary_target_generator.cc
@@ -9,6 +9,7 @@
 #include "tools/gn/err.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/functions.h"
+#include "tools/gn/parse_tree.h"
 #include "tools/gn/scope.h"
 #include "tools/gn/settings.h"
 #include "tools/gn/value_extractors.h"
@@ -60,6 +61,9 @@
   if (!FillCompleteStaticLib())
     return;
 
+  if (!ValidateSources())
+    return;
+
   // Config values (compiler flags, etc.) set directly on this target.
   ConfigValuesGenerator gen(&target_->config_values(), scope_,
                             scope_->GetSourceDir(), err_);
@@ -82,7 +86,6 @@
       case SourceFile::SOURCE_ASM:
       case SourceFile::SOURCE_O:
       case SourceFile::SOURCE_DEF:
-      case SourceFile::SOURCE_RS:
       case SourceFile::SOURCE_GO:
       case SourceFile::SOURCE_RC:
         // These are allowed.
@@ -96,6 +99,8 @@
                     Target::GetStringForOutputType(target_->output_type()) +
                     ". " + source.value() + " is not one of the valid types.");
     }
+
+    target_->source_types_used().Set(source.type());
   }
   return ret;
 }
@@ -211,3 +216,14 @@
     target_->allow_circular_includes_from().insert(cur);
   return true;
 }
+
+bool BinaryTargetGenerator::ValidateSources() {
+  if (target_->source_types_used().MixedSourceUsed()) {
+    *err_ =
+        Err(function_call_, "More than one language used in target sources.",
+            "Mixed sources are not allowed, unless they are "
+            "compilation-compatible (e.g. Objective C and C++).");
+    return false;
+  }
+  return true;
+}
diff --git a/tools/gn/binary_target_generator.h b/tools/gn/binary_target_generator.h
index 6cbd11e..e0700dd 100644
--- a/tools/gn/binary_target_generator.h
+++ b/tools/gn/binary_target_generator.h
@@ -32,6 +32,7 @@
   bool FillOutputDir();
   bool FillOutputExtension();
   bool FillAllowCircularIncludesFrom();
+  bool ValidateSources();
 
   Target::OutputType output_type_;
 
diff --git a/tools/gn/functions_target_unittest.cc b/tools/gn/functions_target_unittest.cc
index 642b8ce..40c6fde 100644
--- a/tools/gn/functions_target_unittest.cc
+++ b/tools/gn/functions_target_unittest.cc
@@ -174,3 +174,23 @@
   good_input.parsed()->Execute(setup.scope(), &err);
   ASSERT_FALSE(err.has_error()) << err.message();
 }
+
+// Checks that we find unused identifiers in targets.
+TEST_F(FunctionsTarget, MixedSourceError) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+
+  // Test a good one first.
+  TestParseInput good_input(
+      "source_set(\"foo\") {\n"
+      "  sources = [ \"cpp.cc\", \"rust.rs\" ]"
+      "}\n");
+  ASSERT_FALSE(good_input.has_error());
+  Err err;
+  good_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "More than one language used in target sources.");
+}
\ No newline at end of file
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index bf3ee35..c1209ed 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -27,21 +27,6 @@
 #include "tools/gn/substitution_writer.h"
 #include "tools/gn/target.h"
 
-bool NinjaBinaryTargetWriter::SourceFileTypeSet::CSourceUsed() {
-  return Get(SourceFile::SOURCE_CPP) || Get(SourceFile::SOURCE_H) ||
-         Get(SourceFile::SOURCE_C) || Get(SourceFile::SOURCE_M) ||
-         Get(SourceFile::SOURCE_MM) || Get(SourceFile::SOURCE_RC) ||
-         Get(SourceFile::SOURCE_S);
-}
-
-bool NinjaBinaryTargetWriter::SourceFileTypeSet::RustSourceUsed() {
-  return Get(SourceFile::SOURCE_RS);
-}
-
-bool NinjaBinaryTargetWriter::SourceFileTypeSet::GoSourceUsed() {
-  return Get(SourceFile::SOURCE_GO);
-}
-
 NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
                                                  std::ostream& out)
     : NinjaTargetWriter(target, out),
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index 24d7ef1..f0611d7 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -13,33 +13,11 @@
 #include "tools/gn/unique_vector.h"
 
 struct EscapeOptions;
-class SourceFileTypeSet;
 
 // Writes a .ninja file for a binary target type (an executable, a shared
 // library, or a static library).
 class NinjaBinaryTargetWriter : public NinjaTargetWriter {
  public:
-  // Represents a set of tool types.
-  class SourceFileTypeSet {
-   public:
-    SourceFileTypeSet() {
-      memset(flags_, 0,
-             sizeof(bool) * static_cast<int>(SourceFile::SOURCE_NUMTYPES));
-    }
-
-    void Set(SourceFile::Type type) { flags_[static_cast<int>(type)] = true; }
-    bool Get(SourceFile::Type type) const {
-      return flags_[static_cast<int>(type)];
-    }
-
-    bool CSourceUsed();
-    bool RustSourceUsed();
-    bool GoSourceUsed();
-
-   private:
-    bool flags_[static_cast<int>(SourceFile::SOURCE_NUMTYPES)];
-  };
-
   NinjaBinaryTargetWriter(const Target* target, std::ostream& out);
   ~NinjaBinaryTargetWriter() override;
 
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index bb059e1..7e92ad4 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -23,6 +23,8 @@
   // dependents to link.
   target.sources().push_back(SourceFile("//foo/input3.o"));
   target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_O);
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
index 4e8217b..041c0a1 100644
--- a/tools/gn/ninja_c_binary_target_writer.cc
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -57,7 +57,6 @@
 void AddSourceSetObjectFiles(const Target* source_set,
                              UniqueVector<OutputFile>* obj_files) {
   std::vector<OutputFile> tool_outputs;  // Prevent allocation in loop.
-  NinjaBinaryTargetWriter::SourceFileTypeSet used_types;
 
   // Compute object files for all sources. Only link the first output from
   // the tool if there are more than one.
@@ -65,28 +64,26 @@
     const char* tool_name = Tool::kToolNone;
     if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
       obj_files->push_back(tool_outputs[0]);
-
-    used_types.Set(source.type());
   }
 
   // Add MSVC precompiled header object files. GCC .gch files are not object
   // files so they are omitted.
   if (source_set->config_values().has_precompiled_headers()) {
-    if (used_types.Get(SourceFile::SOURCE_C)) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) {
       const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
       if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
         GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
         obj_files->Append(tool_outputs.begin(), tool_outputs.end());
       }
     }
-    if (used_types.Get(SourceFile::SOURCE_CPP)) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) {
       const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
       if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
         GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
         obj_files->Append(tool_outputs.begin(), tool_outputs.end());
       }
     }
-    if (used_types.Get(SourceFile::SOURCE_M)) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) {
       const CTool* tool =
           source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
       if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
@@ -94,7 +91,7 @@
         obj_files->Append(tool_outputs.begin(), tool_outputs.end());
       }
     }
-    if (used_types.Get(SourceFile::SOURCE_MM)) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) {
       const CTool* tool =
           source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
       if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
@@ -115,12 +112,7 @@
 NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
 
 void NinjaCBinaryTargetWriter::Run() {
-  // Figure out what source types are needed.
-  SourceFileTypeSet used_types;
-  for (const auto& source : target_->sources())
-    used_types.Set(source.type());
-
-  WriteCompilerVars(used_types);
+  WriteCompilerVars();
 
   OutputFile input_dep = WriteInputsStampAndGetDep();
 
@@ -163,7 +155,7 @@
   // |pch_other_files|. This is to prevent linking against them.
   std::vector<OutputFile> pch_obj_files;
   std::vector<OutputFile> pch_other_files;
-  WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
+  WritePCHCommands(input_dep, order_only_deps, &pch_obj_files,
                    &pch_other_files);
   std::vector<OutputFile>* pch_files =
       !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
@@ -206,8 +198,7 @@
   }
 }
 
-void NinjaCBinaryTargetWriter::WriteCompilerVars(
-    const SourceFileTypeSet& used_types) {
+void NinjaCBinaryTargetWriter::WriteCompilerVars() {
   const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
 
   // Defines.
@@ -234,34 +225,34 @@
       target_->config_values().has_precompiled_headers();
 
   EscapeOptions opts = GetFlagOptions();
-  if (used_types.Get(SourceFile::SOURCE_S) ||
-      used_types.Get(SourceFile::SOURCE_ASM)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_S) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_ASM)) {
     WriteOneFlag(target_, &CSubstitutionAsmFlags, false, Tool::kToolNone,
                  &ConfigValues::asmflags, opts, path_output_, out_);
   }
-  if (used_types.Get(SourceFile::SOURCE_C) ||
-      used_types.Get(SourceFile::SOURCE_CPP) ||
-      used_types.Get(SourceFile::SOURCE_M) ||
-      used_types.Get(SourceFile::SOURCE_MM)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_M) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
     WriteOneFlag(target_, &CSubstitutionCFlags, false, Tool::kToolNone,
                  &ConfigValues::cflags, opts, path_output_, out_);
   }
-  if (used_types.Get(SourceFile::SOURCE_C)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_C)) {
     WriteOneFlag(target_, &CSubstitutionCFlagsC, has_precompiled_headers,
                  CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
                  out_);
   }
-  if (used_types.Get(SourceFile::SOURCE_CPP)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_CPP)) {
     WriteOneFlag(target_, &CSubstitutionCFlagsCc, has_precompiled_headers,
                  CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
                  out_);
   }
-  if (used_types.Get(SourceFile::SOURCE_M)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_M)) {
     WriteOneFlag(target_, &CSubstitutionCFlagsObjC, has_precompiled_headers,
                  CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
                  path_output_, out_);
   }
-  if (used_types.Get(SourceFile::SOURCE_MM)) {
+  if (target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
     WriteOneFlag(target_, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
                  CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
                  path_output_, out_);
@@ -311,7 +302,6 @@
 }
 
 void NinjaCBinaryTargetWriter::WritePCHCommands(
-    const SourceFileTypeSet& used_types,
     const OutputFile& input_dep,
     const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* object_files,
@@ -321,14 +311,14 @@
 
   const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
   if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
-      used_types.Get(SourceFile::SOURCE_C)) {
+      target_->source_types_used().Get(SourceFile::SOURCE_C)) {
     WritePCHCommand(&CSubstitutionCFlagsC, CTool::kCToolCc,
                     tool_c->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
   }
   const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
   if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
-      used_types.Get(SourceFile::SOURCE_CPP)) {
+      target_->source_types_used().Get(SourceFile::SOURCE_CPP)) {
     WritePCHCommand(&CSubstitutionCFlagsCc, CTool::kCToolCxx,
                     tool_cxx->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
@@ -336,7 +326,7 @@
 
   const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
   if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
-      used_types.Get(SourceFile::SOURCE_M)) {
+      target_->source_types_used().Get(SourceFile::SOURCE_M)) {
     WritePCHCommand(&CSubstitutionCFlagsObjC, CTool::kCToolObjC,
                     tool_objc->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
@@ -345,7 +335,7 @@
   const CTool* tool_objcxx =
       target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
   if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
-      used_types.Get(SourceFile::SOURCE_MM)) {
+      target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
     WritePCHCommand(&CSubstitutionCFlagsObjCc, CTool::kCToolObjCxx,
                     tool_objcxx->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
diff --git a/tools/gn/ninja_c_binary_target_writer.h b/tools/gn/ninja_c_binary_target_writer.h
index 4d1bdfa..d236dd9 100644
--- a/tools/gn/ninja_c_binary_target_writer.h
+++ b/tools/gn/ninja_c_binary_target_writer.h
@@ -26,7 +26,7 @@
   typedef std::set<OutputFile> OutputFileSet;
 
   // Writes all flags for the compiler: includes, defines, cflags, etc.
-  void WriteCompilerVars(const SourceFileTypeSet& used_types);
+  void WriteCompilerVars();
 
   // Writes to the output stream a stamp rule for inputs, and
   // returns the file to be appended to source rules that encodes the
@@ -41,8 +41,7 @@
   //
   // input_dep is the stamp file collecting the dependencies required before
   // compiling this target. It will be empty if there are no input deps.
-  void WritePCHCommands(const SourceFileTypeSet& used_types,
-                        const OutputFile& input_dep,
+  void WritePCHCommands(const OutputFile& input_dep,
                         const std::vector<OutputFile>& order_only_deps,
                         std::vector<OutputFile>* object_files,
                         std::vector<OutputFile>* other_files);
diff --git a/tools/gn/ninja_c_binary_target_writer_unittest.cc b/tools/gn/ninja_c_binary_target_writer_unittest.cc
index 829114a..3281176 100644
--- a/tools/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_c_binary_target_writer_unittest.cc
@@ -32,6 +32,8 @@
   // dependents to link.
   target.sources().push_back(SourceFile("//foo/input3.o"));
   target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_O);
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
@@ -182,6 +184,7 @@
 
   TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
   target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
   target.config_values().arflags().push_back("--asdf");
   ASSERT_TRUE(target.OnResolved(&err));
 
@@ -214,11 +217,13 @@
 
   TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
   target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
   target.config_values().arflags().push_back("--asdf");
   target.set_complete_static_lib(true);
 
   TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
   baz.sources().push_back(SourceFile("//foo/input2.cc"));
+  baz.source_types_used().Set(SourceFile::SOURCE_CPP);
 
   target.public_deps().push_back(LabelTargetPair(&baz));
 
@@ -303,6 +308,7 @@
   target.set_output_dir(SourceDir("//out/Debug/foo/"));
   target.sources().push_back(SourceFile("//foo/input1.cc"));
   target.sources().push_back(SourceFile("//foo/input2.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
   target.public_deps().push_back(LabelTargetPair(&action));
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
@@ -361,6 +367,7 @@
   gen_obj.set_output_type(Target::SOURCE_SET);
   gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
   gen_obj.sources().push_back(generated_file);
+  gen_obj.source_types_used().Set(SourceFile::SOURCE_CPP);
   gen_obj.visibility().SetPublic();
   gen_obj.private_deps().push_back(LabelTargetPair(&action));
   gen_obj.set_all_headers_public(false);
@@ -397,6 +404,7 @@
   gen_lib.set_output_type(Target::SHARED_LIBRARY);
   gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
   gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
+  gen_lib.source_types_used().Set(SourceFile::SOURCE_H);
   gen_lib.visibility().SetPublic();
   gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
   gen_lib.SetToolchain(setup.toolchain());
@@ -433,6 +441,7 @@
   executable.set_output_type(Target::EXECUTABLE);
   executable.set_output_dir(SourceDir("//out/Debug/foo/"));
   executable.sources().push_back(SourceFile("//foo/main.cc"));
+  executable.source_types_used().Set(SourceFile::SOURCE_CPP);
   executable.private_deps().push_back(LabelTargetPair(&gen_lib));
   executable.SetToolchain(setup.toolchain());
   ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
@@ -513,6 +522,7 @@
   target.set_output_extension(std::string());
   target.sources().push_back(SourceFile("//foo/input1.cc"));
   target.sources().push_back(SourceFile("//foo/input2.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
 
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
@@ -562,6 +572,7 @@
   inter.data_deps().push_back(LabelTargetPair(&data));
   inter.SetToolchain(setup.toolchain());
   inter.sources().push_back(SourceFile("//foo/inter.cc"));
+  inter.source_types_used().Set(SourceFile::SOURCE_CPP);
   ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
 
   // Write out the intermediate target.
@@ -593,6 +604,7 @@
   exe.public_deps().push_back(LabelTargetPair(&inter));
   exe.SetToolchain(setup.toolchain());
   exe.sources().push_back(SourceFile("//foo/final.cc"));
+  exe.source_types_used().Set(SourceFile::SOURCE_CPP);
   ASSERT_TRUE(exe.OnResolved(&err));
 
   std::ostringstream final_out;
@@ -633,6 +645,8 @@
   shared_lib.SetToolchain(setup.toolchain());
   shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
   shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
+  shared_lib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  shared_lib.source_types_used().Set(SourceFile::SOURCE_DEF);
   ASSERT_TRUE(shared_lib.OnResolved(&err));
 
   std::ostringstream out;
@@ -667,6 +681,7 @@
   loadable_module.visibility().SetPublic();
   loadable_module.SetToolchain(setup.toolchain());
   loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
+  loadable_module.source_types_used().Set(SourceFile::SOURCE_CPP);
   ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
 
   std::ostringstream out;
@@ -697,6 +712,7 @@
   exe.public_deps().push_back(LabelTargetPair(&loadable_module));
   exe.SetToolchain(setup.toolchain());
   exe.sources().push_back(SourceFile("//foo/final.cc"));
+  exe.source_types_used().Set(SourceFile::SOURCE_CPP);
   ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
 
   std::ostringstream final_out;
@@ -770,6 +786,8 @@
     no_pch_target.visibility().SetPublic();
     no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
     no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_C);
     no_pch_target.config_values().cflags_c().push_back("-std=c99");
     no_pch_target.SetToolchain(&pch_toolchain);
     ASSERT_TRUE(no_pch_target.OnResolved(&err));
@@ -807,6 +825,8 @@
     pch_target.visibility().SetPublic();
     pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
     pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    pch_target.source_types_used().Set(SourceFile::SOURCE_C);
     pch_target.SetToolchain(&pch_toolchain);
     ASSERT_TRUE(pch_target.OnResolved(&err));
 
@@ -900,6 +920,8 @@
     no_pch_target.visibility().SetPublic();
     no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
     no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_C);
     no_pch_target.config_values().cflags_c().push_back("-std=c99");
     no_pch_target.SetToolchain(&pch_toolchain);
     ASSERT_TRUE(no_pch_target.OnResolved(&err));
@@ -937,6 +959,8 @@
     pch_target.visibility().SetPublic();
     pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
     pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    pch_target.source_types_used().Set(SourceFile::SOURCE_C);
     pch_target.SetToolchain(&pch_toolchain);
     ASSERT_TRUE(pch_target.OnResolved(&err));
 
@@ -985,6 +1009,7 @@
   TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
   target.sources().push_back(SourceFile("//a.cc"));
   target.sources().push_back(SourceFile("//a.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
 
   EXPECT_FALSE(scheduler().is_failed());
 
@@ -1013,6 +1038,7 @@
     target.visibility().SetPublic();
     target.sources().push_back(SourceFile("//foo/input1.cc"));
     target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
     target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
     target.SetToolchain(setup.toolchain());
     ASSERT_TRUE(target.OnResolved(&err));
@@ -1078,6 +1104,7 @@
     target.visibility().SetPublic();
     target.sources().push_back(SourceFile("//foo/input1.cc"));
     target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
     target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
     target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
     target.SetToolchain(setup.toolchain());
@@ -1126,6 +1153,7 @@
     target.visibility().SetPublic();
     target.sources().push_back(SourceFile("//foo/input1.cc"));
     target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
     target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
     target.configs().push_back(LabelConfigPair(&config));
     target.SetToolchain(setup.toolchain());
diff --git a/tools/gn/source_file.cc b/tools/gn/source_file.cc
index 266a072..f925546 100644
--- a/tools/gn/source_file.cc
+++ b/tools/gn/source_file.cc
@@ -99,3 +99,30 @@
   value_ = value;
   type_ = GetSourceFileType(value_);
 }
+
+SourceFileTypeSet::SourceFileTypeSet() : empty_(true) {
+  memset(flags_, 0,
+         sizeof(bool) * static_cast<int>(SourceFile::SOURCE_NUMTYPES));
+}
+
+bool SourceFileTypeSet::CSourceUsed() const {
+  return empty_ || Get(SourceFile::SOURCE_CPP) || Get(SourceFile::SOURCE_H) ||
+         Get(SourceFile::SOURCE_C) || Get(SourceFile::SOURCE_M) ||
+         Get(SourceFile::SOURCE_MM) || Get(SourceFile::SOURCE_RC) ||
+         Get(SourceFile::SOURCE_S) || Get(SourceFile::SOURCE_O) ||
+         Get(SourceFile::SOURCE_DEF);
+}
+
+bool SourceFileTypeSet::RustSourceUsed() const {
+  return Get(SourceFile::SOURCE_RS);
+}
+
+bool SourceFileTypeSet::GoSourceUsed() const {
+  return Get(SourceFile::SOURCE_GO);
+}
+
+bool SourceFileTypeSet::MixedSourceUsed() const {
+  return (1 << static_cast<int>(CSourceUsed())
+            << static_cast<int>(RustSourceUsed())
+            << static_cast<int>(GoSourceUsed())) > 2;
+}
diff --git a/tools/gn/source_file.h b/tools/gn/source_file.h
index d42063d..ed814c8 100644
--- a/tools/gn/source_file.h
+++ b/tools/gn/source_file.h
@@ -129,4 +129,30 @@
   lhs.swap(rhs);
 }
 
+// Represents a set of tool types.
+class SourceFileTypeSet {
+ public:
+  SourceFileTypeSet();
+
+  void Set(SourceFile::Type type) {
+    flags_[static_cast<int>(type)] = true;
+    empty_ = false;
+  }
+  bool Get(SourceFile::Type type) const {
+    return flags_[static_cast<int>(type)];
+  }
+
+  bool empty() const { return empty_; }
+
+  bool CSourceUsed() const;
+  bool RustSourceUsed() const;
+  bool GoSourceUsed() const;
+
+  bool MixedSourceUsed() const;
+
+ private:
+  bool empty_;
+  bool flags_[static_cast<int>(SourceFile::SOURCE_NUMTYPES)];
+};
+
 #endif  // TOOLS_GN_SOURCE_FILE_H_
diff --git a/tools/gn/target.h b/tools/gn/target.h
index e99d813..1256818 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -125,6 +125,11 @@
   const FileList& sources() const { return sources_; }
   FileList& sources() { return sources_; }
 
+  const SourceFileTypeSet& source_types_used() const {
+    return source_types_used_;
+  }
+  SourceFileTypeSet& source_types_used() { return source_types_used_; }
+
   // Set to true when all sources are public. This is the default. In this case
   // the public headers list should be empty.
   bool all_headers_public() const { return all_headers_public_; }
@@ -367,6 +372,7 @@
   bool output_extension_set_;
 
   FileList sources_;
+  SourceFileTypeSet source_types_used_;
   bool all_headers_public_;
   FileList public_headers_;
   bool check_includes_;