Refactor SubstitutionType for increased flexibility

This removes the SubstitutionType enum and the associated arrays of GN
and Ninja substitution names, replacing them with a more dynamic
collection of data.

This is part of a larger refactoring that will allow for additional
types of Tools (e.g. non-C/C++ langauge compilation) to be added more
simply and cleanly.

Change-Id: I2267a87b16d69790e584e729e426dab594784f71
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4760
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/base/containers/flat_set.h b/base/containers/flat_set.h
new file mode 100644
index 0000000..700617f
--- /dev/null
+++ b/base/containers/flat_set.h
@@ -0,0 +1,141 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_FLAT_SET_H_
+#define BASE_CONTAINERS_FLAT_SET_H_
+
+#include <functional>
+
+#include "base/containers/flat_tree.h"
+#include "base/template_util.h"
+
+namespace base {
+
+// flat_set is a container with a std::set-like interface that stores its
+// contents in a sorted vector.
+//
+// Please see //base/containers/README.md for an overview of which container
+// to select.
+//
+// PROS
+//
+//  - Good memory locality.
+//  - Low overhead, especially for smaller sets.
+//  - Performance is good for more workloads than you might expect (see
+//    overview link above).
+//  - Supports C++14 set interface.
+//
+// CONS
+//
+//  - Inserts and removals are O(n).
+//
+// IMPORTANT NOTES
+//
+//  - Iterators are invalidated across mutations.
+//  - If possible, construct a flat_set in one operation by inserting into
+//    a std::vector and moving that vector into the flat_set constructor.
+//  - For multiple removals use base::EraseIf() which is O(n) rather than
+//    O(n * removed_items).
+//
+// QUICK REFERENCE
+//
+// Most of the core functionality is inherited from flat_tree. Please see
+// flat_tree.h for more details for most of these functions. As a quick
+// reference, the functions available are:
+//
+// Constructors (inputs need not be sorted):
+//   flat_set(InputIterator first, InputIterator last,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare());
+//   flat_set(const flat_set&);
+//   flat_set(flat_set&&);
+//   flat_set(std::vector<Key>,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare());  // Re-use storage.
+//   flat_set(std::initializer_list<value_type> ilist,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& comp = Compare());
+//
+// Assignment functions:
+//   flat_set& operator=(const flat_set&);
+//   flat_set& operator=(flat_set&&);
+//   flat_set& operator=(initializer_list<Key>);
+//
+// Memory management functions:
+//   void   reserve(size_t);
+//   size_t capacity() const;
+//   void   shrink_to_fit();
+//
+// Size management functions:
+//   void   clear();
+//   size_t size() const;
+//   size_t max_size() const;
+//   bool   empty() const;
+//
+// Iterator functions:
+//   iterator               begin();
+//   const_iterator         begin() const;
+//   const_iterator         cbegin() const;
+//   iterator               end();
+//   const_iterator         end() const;
+//   const_iterator         cend() const;
+//   reverse_iterator       rbegin();
+//   const reverse_iterator rbegin() const;
+//   const_reverse_iterator crbegin() const;
+//   reverse_iterator       rend();
+//   const_reverse_iterator rend() const;
+//   const_reverse_iterator crend() const;
+//
+// Insert and accessor functions:
+//   pair<iterator, bool> insert(const key_type&);
+//   pair<iterator, bool> insert(key_type&&);
+//   void                 insert(InputIterator first, InputIterator last,
+//                               FlatContainerDupes = KEEP_FIRST_OF_DUPES);
+//   iterator             insert(const_iterator hint, const key_type&);
+//   iterator             insert(const_iterator hint, key_type&&);
+//   pair<iterator, bool> emplace(Args&&...);
+//   iterator             emplace_hint(const_iterator, Args&&...);
+//
+// Erase functions:
+//   iterator erase(iterator);
+//   iterator erase(const_iterator);
+//   iterator erase(const_iterator first, const_iterator& last);
+//   template <typename K> size_t erase(const K& key);
+//
+// Comparators (see std::set documentation).
+//   key_compare   key_comp() const;
+//   value_compare value_comp() const;
+//
+// Search functions:
+//   template <typename K> size_t                   count(const K&) const;
+//   template <typename K> iterator                 find(const K&);
+//   template <typename K> const_iterator           find(const K&) const;
+//   template <typename K> bool                     contains(const K&) const;
+//   template <typename K> pair<iterator, iterator> equal_range(K&);
+//   template <typename K> iterator                 lower_bound(const K&);
+//   template <typename K> const_iterator           lower_bound(const K&) const;
+//   template <typename K> iterator                 upper_bound(const K&);
+//   template <typename K> const_iterator           upper_bound(const K&) const;
+//
+// General functions:
+//   void swap(flat_set&&);
+//
+// Non-member operators:
+//   bool operator==(const flat_set&, const flat_set);
+//   bool operator!=(const flat_set&, const flat_set);
+//   bool operator<(const flat_set&, const flat_set);
+//   bool operator>(const flat_set&, const flat_set);
+//   bool operator>=(const flat_set&, const flat_set);
+//   bool operator<=(const flat_set&, const flat_set);
+//
+template <class Key, class Compare = std::less<>>
+using flat_set = typename ::base::internal::flat_tree<
+    Key,
+    Key,
+    ::base::internal::GetKeyFromValueIdentity<Key>,
+    Compare>;
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_FLAT_SET_H_
\ No newline at end of file
diff --git a/build/gen.py b/build/gen.py
index eeb7cb5..874ecba 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -425,6 +425,7 @@
         'tools/gn/bundle_data_target_generator.cc',
         'tools/gn/bundle_file_rule.cc',
         'tools/gn/c_include_iterator.cc',
+        'tools/gn/c_substitution_type.cc',
         'tools/gn/c_tool.cc',
         'tools/gn/command_analyze.cc',
         'tools/gn/command_args.cc',
diff --git a/tools/gn/action_target_generator.cc b/tools/gn/action_target_generator.cc
index c0ba6c8..61e9d09 100644
--- a/tools/gn/action_target_generator.cc
+++ b/tools/gn/action_target_generator.cc
@@ -74,7 +74,7 @@
   const auto& required_args_substitutions =
       target_->action_values().args().required_types();
   bool has_rsp_file_name = base::ContainsValue(required_args_substitutions,
-                                               SUBSTITUTION_RSP_FILE_NAME);
+                                               &SubstitutionRspFileName);
   if (target_->action_values().uses_rsp_file() && !has_rsp_file_name) {
     *err_ = Err(
         function_call_, "Missing {{response_file_name}} in args.",
diff --git a/tools/gn/bundle_data_target_generator.cc b/tools/gn/bundle_data_target_generator.cc
index 23f26fd..cfd2903 100644
--- a/tools/gn/bundle_data_target_generator.cc
+++ b/tools/gn/bundle_data_target_generator.cc
@@ -55,10 +55,10 @@
     return false;
 
   // Check the substitutions used are valid for this purpose.
-  for (SubstitutionType type : outputs.required_types()) {
+  for (const Substitution* type : outputs.required_types()) {
     if (!IsValidBundleDataSubstitution(type)) {
       *err_ = Err(value->origin(), "Invalid substitution type.",
-                  "The substitution " + std::string(kSubstitutionNames[type]) +
+                  "The substitution " + std::string(type->name) +
                       " isn't valid for something\n"
                       "operating on a bundle_data file such as this.");
       return false;
diff --git a/tools/gn/bundle_file_rule.cc b/tools/gn/bundle_file_rule.cc
index 5dffb07..2b9881c 100644
--- a/tools/gn/bundle_file_rule.cc
+++ b/tools/gn/bundle_file_rule.cc
@@ -51,47 +51,40 @@
                                           Err* err) const {
   std::string output_path;
   for (const auto& subrange : pattern_.ranges()) {
-    switch (subrange.type) {
-      case SUBSTITUTION_LITERAL:
-        output_path.append(subrange.literal);
-        break;
-      case SUBSTITUTION_BUNDLE_ROOT_DIR:
-        if (bundle_data.contents_dir().is_null()) {
-          *err = ErrMissingPropertyForExpansion(settings, target, this,
-                                                variables::kBundleRootDir);
-          return false;
-        }
-        output_path.append(bundle_data.root_dir().value());
-        break;
-      case SUBSTITUTION_BUNDLE_CONTENTS_DIR:
-        if (bundle_data.contents_dir().is_null()) {
-          *err = ErrMissingPropertyForExpansion(settings, target, this,
-                                                variables::kBundleContentsDir);
-          return false;
-        }
-        output_path.append(bundle_data.contents_dir().value());
-        break;
-      case SUBSTITUTION_BUNDLE_RESOURCES_DIR:
-        if (bundle_data.resources_dir().is_null()) {
-          *err = ErrMissingPropertyForExpansion(settings, target, this,
-                                                variables::kBundleResourcesDir);
-          return false;
-        }
-        output_path.append(bundle_data.resources_dir().value());
-        break;
-      case SUBSTITUTION_BUNDLE_EXECUTABLE_DIR:
-        if (bundle_data.executable_dir().is_null()) {
-          *err = ErrMissingPropertyForExpansion(
-              settings, target, this, variables::kBundleExecutableDir);
-          return false;
-        }
-        output_path.append(bundle_data.executable_dir().value());
-        break;
-      default:
-        output_path.append(SubstitutionWriter::GetSourceSubstitution(
-            target_, target_->settings(), source_file, subrange.type,
-            SubstitutionWriter::OUTPUT_ABSOLUTE, SourceDir()));
-        break;
+    if (subrange.type == &SubstitutionLiteral) {
+      output_path.append(subrange.literal);
+    } else if (subrange.type == &SubstitutionBundleRootDir) {
+      if (bundle_data.contents_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleRootDir);
+        return false;
+      }
+      output_path.append(bundle_data.root_dir().value());
+    } else if (subrange.type == &SubstitutionBundleContentsDir) {
+      if (bundle_data.contents_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleContentsDir);
+        return false;
+      }
+      output_path.append(bundle_data.contents_dir().value());
+    } else if (subrange.type == &SubstitutionBundleResourcesDir) {
+      if (bundle_data.resources_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleResourcesDir);
+        return false;
+      }
+      output_path.append(bundle_data.resources_dir().value());
+    } else if (subrange.type == &SubstitutionBundleExecutableDir) {
+      if (bundle_data.executable_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleExecutableDir);
+        return false;
+      }
+      output_path.append(bundle_data.executable_dir().value());
+    } else {
+      output_path.append(SubstitutionWriter::GetSourceSubstitution(
+          target_, target_->settings(), source_file, subrange.type,
+          SubstitutionWriter::OUTPUT_ABSOLUTE, SourceDir()));
     }
   }
   *expanded_source_file = SourceFile(SourceFile::SWAP_IN, &output_path);
diff --git a/tools/gn/c_substitution_type.cc b/tools/gn/c_substitution_type.cc
new file mode 100644
index 0000000..3054124
--- /dev/null
+++ b/tools/gn/c_substitution_type.cc
@@ -0,0 +1,86 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/c_substitution_type.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "tools/gn/err.h"
+
+const SubstitutionTypes CSubstitutions = {
+    &CSubstitutionAsmFlags,     &CSubstitutionCFlags,
+    &CSubstitutionCFlagsC,      &CSubstitutionCFlagsCc,
+    &CSubstitutionCFlagsObjC,   &CSubstitutionCFlagsObjCc,
+    &CSubstitutionDefines,      &CSubstitutionIncludeDirs,
+
+    &CSubstitutionLinkerInputs, &CSubstitutionLinkerInputsNewline,
+    &CSubstitutionLdFlags,      &CSubstitutionLibs,
+    &CSubstitutionOutputDir,    &CSubstitutionOutputExtension,
+    &CSubstitutionSoLibs,
+
+    &CSubstitutionArFlags,
+};
+
+// Valid for compiler tools.
+const Substitution CSubstitutionAsmFlags = {"{{asmflags}}", "asmflags"};
+const Substitution CSubstitutionCFlags = {"{{cflags}}", "cflags"};
+const Substitution CSubstitutionCFlagsC = {"{{cflags_c}}", "cflags_c"};
+const Substitution CSubstitutionCFlagsCc = {"{{cflags_cc}}", "cflags_cc"};
+const Substitution CSubstitutionCFlagsObjC = {"{{cflags_objc}}", "cflags_objc"};
+const Substitution CSubstitutionCFlagsObjCc = {"{{cflags_objcc}}",
+                                              "cflags_objcc"};
+const Substitution CSubstitutionDefines = {"{{defines}}", "defines"};
+const Substitution CSubstitutionIncludeDirs = {"{{include_dirs}}",
+                                              "include_dirs"};
+
+// Valid for linker tools.
+const Substitution CSubstitutionLinkerInputs = {"{{inputs}}", "in"};
+const Substitution CSubstitutionLinkerInputsNewline = {"{{inputs_newline}}",
+                                                      "in_newline"};
+const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
+const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
+const Substitution CSubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
+const Substitution CSubstitutionOutputExtension = {"{{output_extension}}",
+                                                  "output_extension"};
+const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
+
+// Valid for alink only.
+const Substitution CSubstitutionArFlags = {"{{arflags}}", "arflags"};
+
+bool IsValidCompilerSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
+         type == &SubstitutionSource || type == &CSubstitutionAsmFlags ||
+         type == &CSubstitutionCFlags || type == &CSubstitutionCFlagsC ||
+         type == &CSubstitutionCFlagsCc || type == &CSubstitutionCFlagsObjC ||
+         type == &CSubstitutionCFlagsObjCc || type == &CSubstitutionDefines ||
+         type == &CSubstitutionIncludeDirs;
+}
+
+bool IsValidCompilerOutputsSubstitution(const Substitution* type) {
+  // All tool types except "output" (which would be infinitely recursive).
+  return (IsValidToolSubstitution(type) && type != &SubstitutionOutput) ||
+         IsValidSourceSubstitution(type);
+}
+
+bool IsValidLinkerSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+         type == &CSubstitutionLinkerInputsNewline ||
+         type == &CSubstitutionLdFlags || type == &CSubstitutionLibs ||
+         type == &CSubstitutionOutputDir ||
+         type == &CSubstitutionOutputExtension || type == &CSubstitutionSoLibs;
+}
+
+bool IsValidLinkerOutputsSubstitution(const Substitution* type) {
+  // All valid compiler outputs plus the output extension.
+  return IsValidCompilerOutputsSubstitution(type) ||
+         type == &CSubstitutionOutputDir || type == &CSubstitutionOutputExtension;
+}
+
+bool IsValidALinkSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+         type == &CSubstitutionLinkerInputsNewline ||
+         type == &CSubstitutionArFlags || type == &CSubstitutionOutputDir ||
+         type == &CSubstitutionOutputExtension;
+}
diff --git a/tools/gn/c_substitution_type.h b/tools/gn/c_substitution_type.h
new file mode 100644
index 0000000..eee5304
--- /dev/null
+++ b/tools/gn/c_substitution_type.h
@@ -0,0 +1,45 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_C_SUBSTITUTION_TYPE_H_
+#define TOOLS_GN_C_SUBSTITUTION_TYPE_H_
+
+#include <set>
+#include <vector>
+
+#include "tools/gn/substitution_type.h"
+
+// The set of substitutions available to all tools.
+extern const SubstitutionTypes CSubstitutions;
+
+// Valid for compiler tools.
+extern const Substitution CSubstitutionAsmFlags;
+extern const Substitution CSubstitutionCFlags;
+extern const Substitution CSubstitutionCFlagsC;
+extern const Substitution CSubstitutionCFlagsCc;
+extern const Substitution CSubstitutionCFlagsObjC;
+extern const Substitution CSubstitutionCFlagsObjCc;
+extern const Substitution CSubstitutionDefines;
+extern const Substitution CSubstitutionIncludeDirs;
+
+// Valid for linker tools.
+extern const Substitution CSubstitutionLinkerInputs;
+extern const Substitution CSubstitutionLinkerInputsNewline;
+extern const Substitution CSubstitutionLdFlags;
+extern const Substitution CSubstitutionLibs;
+extern const Substitution CSubstitutionOutputDir;
+extern const Substitution CSubstitutionOutputExtension;
+extern const Substitution CSubstitutionSoLibs;
+
+// Valid for alink only.
+extern const Substitution CSubstitutionArFlags;
+
+// Both compiler and linker tools.
+bool IsValidCompilerSubstitution(const Substitution* type);
+bool IsValidCompilerOutputsSubstitution(const Substitution* type);
+bool IsValidLinkerSubstitution(const Substitution* type);
+bool IsValidLinkerOutputsSubstitution(const Substitution* type);
+bool IsValidALinkSubstitution(const Substitution* type);
+
+#endif  // TOOLS_GN_C_SUBSTITUTION_TYPE_H_
diff --git a/tools/gn/c_tool.cc b/tools/gn/c_tool.cc
index 6167286..59452fb 100644
--- a/tools/gn/c_tool.cc
+++ b/tools/gn/c_tool.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "tools/gn/c_tool.h"
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/target.h"
 
 const char* CTool::kCToolCc = "cc";
@@ -155,8 +156,7 @@
   for (const auto& cur_type : list.required_types()) {
     if (!ValidateOutputSubstitution(cur_type)) {
       *err = Err(*value, "Pattern not valid here.",
-                 "You used the pattern " +
-                     std::string(kSubstitutionNames[cur_type]) +
+                 "You used the pattern " + std::string(cur_type->name) +
                      " which is not valid\nfor this variable.");
       return false;
     }
@@ -206,7 +206,7 @@
   return true;
 }
 
-bool CTool::ValidateSubstitution(SubstitutionType sub_type) const {
+bool CTool::ValidateSubstitution(const Substitution* sub_type) const {
   if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolObjC ||
       name_ == kCToolObjCxx || name_ == kCToolRc || name_ == kCToolAsm)
     return IsValidCompilerSubstitution(sub_type);
@@ -219,7 +219,7 @@
   return false;
 }
 
-bool CTool::ValidateOutputSubstitution(SubstitutionType sub_type) const {
+bool CTool::ValidateOutputSubstitution(const Substitution* sub_type) const {
   if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolObjC ||
       name_ == kCToolObjCxx || name_ == kCToolRc || name_ == kCToolAsm)
     return IsValidCompilerOutputsSubstitution(sub_type);
diff --git a/tools/gn/c_tool.h b/tools/gn/c_tool.h
index 045024c..129a733 100644
--- a/tools/gn/c_tool.h
+++ b/tools/gn/c_tool.h
@@ -46,7 +46,7 @@
   bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
   bool ValidateName(const char* name) const override;
   void SetComplete() override;
-  bool ValidateSubstitution(SubstitutionType sub_type) const override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
 
   CTool* AsC() override;
   const CTool* AsC() const override;
@@ -109,7 +109,7 @@
   // Initialization methods used by InitTool(). If successful, will set the
   // field and return true, otherwise will return false. Must be called before
   // SetComplete().
-  bool ValidateOutputSubstitution(SubstitutionType sub_type) const;
+  bool ValidateOutputSubstitution(const Substitution* sub_type) const;
   bool ValidateRuntimeOutputs(Err* err);
   // Validates either link_output or depend_output. To generalize to either,
   // pass
diff --git a/tools/gn/compile_commands_writer.cc b/tools/gn/compile_commands_writer.cc
index e0af121..e4a1a19 100644
--- a/tools/gn/compile_commands_writer.cc
+++ b/tools/gn/compile_commands_writer.cc
@@ -7,9 +7,10 @@
 #include <sstream>
 
 #include "base/json/string_escape.h"
-#include "base/strings/stringprintf.h"
 #include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
 #include "tools/gn/builder.h"
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/c_tool.h"
 #include "tools/gn/config_values_extractors.h"
 #include "tools/gn/deps_iterator.h"
@@ -68,32 +69,32 @@
   base::EscapeJSONString(includes_out.str(), false, &flags.includes);
 
   std::ostringstream cflags_out;
-  WriteOneFlag(target, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
+  WriteOneFlag(target, &CSubstitutionCFlags, false, Tool::kToolNone,
                &ConfigValues::cflags, opts, path_output, cflags_out,
                /*write_substitution=*/false);
   base::EscapeJSONString(cflags_out.str(), false, &flags.cflags);
 
   std::ostringstream cflags_c_out;
-  WriteOneFlag(target, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
+  WriteOneFlag(target, &CSubstitutionCFlagsC, has_precompiled_headers,
                CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output,
                cflags_c_out, /*write_substitution=*/false);
   base::EscapeJSONString(cflags_c_out.str(), false, &flags.cflags_c);
 
   std::ostringstream cflags_cc_out;
-  WriteOneFlag(target, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
+  WriteOneFlag(target, &CSubstitutionCFlagsCc, has_precompiled_headers,
                CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output,
                cflags_cc_out, /*write_substitution=*/false);
   base::EscapeJSONString(cflags_cc_out.str(), false, &flags.cflags_cc);
 
   std::ostringstream cflags_objc_out;
-  WriteOneFlag(target, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
+  WriteOneFlag(target, &CSubstitutionCFlagsObjC, has_precompiled_headers,
                CTool::kCToolObjC, &ConfigValues::cflags_objc, opts, path_output,
                cflags_objc_out,
                /*write_substitution=*/false);
   base::EscapeJSONString(cflags_objc_out.str(), false, &flags.cflags_objc);
 
   std::ostringstream cflags_objcc_out;
-  WriteOneFlag(target, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
+  WriteOneFlag(target, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
                CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
                path_output, cflags_objcc_out, /*write_substitution=*/false);
   base::EscapeJSONString(cflags_objcc_out.str(), false, &flags.cflags_objcc);
@@ -132,65 +133,53 @@
 
   for (const auto& range : tool->command().ranges()) {
     // TODO: this is emitting a bonus space prior to each substitution.
-    switch (range.type) {
-      case SUBSTITUTION_LITERAL:
-        EscapeStringToStream(command_out, range.literal, no_quoting);
-        break;
-      case SUBSTITUTION_OUTPUT:
-        path_output.WriteFiles(command_out, tool_outputs);
-        break;
-      case SUBSTITUTION_DEFINES:
-        command_out << flags.defines;
-        break;
-      case SUBSTITUTION_INCLUDE_DIRS:
-        command_out << flags.includes;
-        break;
-      case SUBSTITUTION_CFLAGS:
-        command_out << flags.cflags;
-        break;
-      case SUBSTITUTION_CFLAGS_C:
-        if (source_type == SOURCE_C)
-          command_out << flags.cflags_c;
-        break;
-      case SUBSTITUTION_CFLAGS_CC:
-        if (source_type == SOURCE_CPP)
-          command_out << flags.cflags_cc;
-        break;
-      case SUBSTITUTION_CFLAGS_OBJC:
-        if (source_type == SOURCE_M)
-          command_out << flags.cflags_objc;
-        break;
-      case SUBSTITUTION_CFLAGS_OBJCC:
-        if (source_type == SOURCE_MM)
-          command_out << flags.cflags_objcc;
-        break;
-      case SUBSTITUTION_LABEL:
-      case SUBSTITUTION_LABEL_NAME:
-      case SUBSTITUTION_ROOT_GEN_DIR:
-      case SUBSTITUTION_ROOT_OUT_DIR:
-      case SUBSTITUTION_TARGET_GEN_DIR:
-      case SUBSTITUTION_TARGET_OUT_DIR:
-      case SUBSTITUTION_TARGET_OUTPUT_NAME:
-      case SUBSTITUTION_SOURCE:
-      case SUBSTITUTION_SOURCE_NAME_PART:
-      case SUBSTITUTION_SOURCE_FILE_PART:
-      case SUBSTITUTION_SOURCE_DIR:
-      case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
-      case SUBSTITUTION_SOURCE_GEN_DIR:
-      case SUBSTITUTION_SOURCE_OUT_DIR:
-      case SUBSTITUTION_SOURCE_TARGET_RELATIVE:
-        EscapeStringToStream(command_out,
-                             SubstitutionWriter::GetCompilerSubstitution(
-                                 target, source, range.type),
-                             opts);
-        break;
-
+    if (range.type == &SubstitutionLiteral) {
+      EscapeStringToStream(command_out, range.literal, no_quoting);
+    } else if (range.type == &SubstitutionOutput) {
+      path_output.WriteFiles(command_out, tool_outputs);
+    } else if (range.type == &CSubstitutionDefines) {
+      command_out << flags.defines;
+    } else if (range.type == &CSubstitutionIncludeDirs) {
+      command_out << flags.includes;
+    } else if (range.type == &CSubstitutionCFlags) {
+      command_out << flags.cflags;
+    } else if (range.type == &CSubstitutionCFlagsC) {
+      if (source_type == SOURCE_C)
+        command_out << flags.cflags_c;
+    } else if (range.type == &CSubstitutionCFlagsCc) {
+      if (source_type == SOURCE_CPP)
+        command_out << flags.cflags_cc;
+    } else if (range.type == &CSubstitutionCFlagsObjC) {
+      if (source_type == SOURCE_M)
+        command_out << flags.cflags_objc;
+    } else if (range.type == &CSubstitutionCFlagsObjCc) {
+      if (source_type == SOURCE_MM)
+        command_out << flags.cflags_objcc;
+    } else if (range.type == &SubstitutionLabel ||
+               range.type == &SubstitutionLabelName ||
+               range.type == &SubstitutionRootGenDir ||
+               range.type == &SubstitutionRootOutDir ||
+               range.type == &SubstitutionTargetGenDir ||
+               range.type == &SubstitutionTargetOutDir ||
+               range.type == &SubstitutionTargetOutputName ||
+               range.type == &SubstitutionSource ||
+               range.type == &SubstitutionSourceNamePart ||
+               range.type == &SubstitutionSourceFilePart ||
+               range.type == &SubstitutionSourceDir ||
+               range.type == &SubstitutionSourceRootRelativeDir ||
+               range.type == &SubstitutionSourceGenDir ||
+               range.type == &SubstitutionSourceOutDir ||
+               range.type == &SubstitutionSourceTargetRelative) {
+      EscapeStringToStream(command_out,
+                           SubstitutionWriter::GetCompilerSubstitution(
+                               target, source, range.type),
+                           opts);
+    } else {
       // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++
       // source files.
-      default:
-        NOTREACHED() << "Unsupported substitution for this type of target : "
-                     << kSubstitutionNames[range.type];
-        continue;
+      NOTREACHED() << "Unsupported substitution for this type of target : "
+                   << range.type->name;
+      continue;
     }
   }
   compile_commands->append(kPrettyPrintLineEnding);
@@ -300,7 +289,6 @@
   if (!WriteFileIfChanged(output_path, json, err))
     return false;
   return true;
-
 }
 
 std::vector<const Target*> CompileCommandsWriter::FilterTargets(
diff --git a/tools/gn/function_process_file_template.cc b/tools/gn/function_process_file_template.cc
index 47de640..7035503 100644
--- a/tools/gn/function_process_file_template.cc
+++ b/tools/gn/function_process_file_template.cc
@@ -95,7 +95,7 @@
   }
 
   auto& types = subst.required_types();
-  if (base::ContainsValue(types, SUBSTITUTION_SOURCE_TARGET_RELATIVE)) {
+  if (base::ContainsValue(types, &SubstitutionSourceTargetRelative)) {
     *err = Err(template_arg, "Not a valid substitution type for the function.");
     return Value();
   }
diff --git a/tools/gn/general_tool.cc b/tools/gn/general_tool.cc
index d89dd30..a0f285d 100644
--- a/tools/gn/general_tool.cc
+++ b/tools/gn/general_tool.cc
@@ -39,7 +39,7 @@
   return Tool::InitTool(scope, toolchain, err);
 }
 
-bool GeneralTool::ValidateSubstitution(SubstitutionType sub_type) const {
+bool GeneralTool::ValidateSubstitution(const Substitution* sub_type) const {
   if (name_ == kGeneralToolStamp || name_ == kGeneralToolAction)
     return IsValidToolSubstitution(sub_type);
   else if (name_ == kGeneralToolCopy || name_ == kGeneralToolCopyBundleData)
diff --git a/tools/gn/general_tool.h b/tools/gn/general_tool.h
index df9caf2..b6e1af0 100644
--- a/tools/gn/general_tool.h
+++ b/tools/gn/general_tool.h
@@ -35,7 +35,7 @@
   bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
   bool ValidateName(const char* name) const override;
   void SetComplete() override;
-  bool ValidateSubstitution(SubstitutionType sub_type) const override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
 
   GeneralTool* AsGeneral() override;
   const GeneralTool* AsGeneral() const override;
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
index b96e724..f6ffd15 100644
--- a/tools/gn/ninja_c_binary_target_writer.cc
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -13,12 +13,13 @@
 #include <unordered_set>
 
 #include "base/strings/string_util.h"
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/config_values_extractors.h"
 #include "tools/gn/deps_iterator.h"
 #include "tools/gn/err.h"
 #include "tools/gn/escape.h"
-#include "tools/gn/general_tool.h"
 #include "tools/gn/filesystem_utils.h"
+#include "tools/gn/general_tool.h"
 #include "tools/gn/ninja_target_command_util.h"
 #include "tools/gn/ninja_utils.h"
 #include "tools/gn/scheduler.h"
@@ -41,15 +42,15 @@
 // precompiled header files.
 const char* GetPCHLangForToolType(const char* name) {
   if (name == CTool::kCToolCc)
-      return "c-header";
+    return "c-header";
   if (name == CTool::kCToolCxx)
-      return "c++-header";
+    return "c++-header";
   if (name == CTool::kCToolObjC)
-      return "objective-c-header";
+    return "objective-c-header";
   if (name == CTool::kCToolObjCxx)
-      return "objective-c++-header";
-      NOTREACHED() << "Not a valid PCH tool type: " << name;
-      return "";
+    return "objective-c++-header";
+  NOTREACHED() << "Not a valid PCH tool type: " << name;
+  return "";
 }
 
 // Appends the object files generated by the given source set to the given
@@ -87,7 +88,8 @@
       }
     }
     if (used_types.Get(SOURCE_M)) {
-      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
+      const CTool* tool =
+          source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
       if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
         GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
         obj_files->Append(tool_outputs.begin(), tool_outputs.end());
@@ -210,16 +212,16 @@
   const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
 
   // Defines.
-  if (subst.used[SUBSTITUTION_DEFINES]) {
-    out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
+  if (subst.used.count(&CSubstitutionDefines)) {
+    out_ << CSubstitutionDefines.ninja_name << " =";
     RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
                                                DefineWriter(), out_);
     out_ << std::endl;
   }
 
   // Include directories.
-  if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
-    out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
+  if (subst.used.count(&CSubstitutionIncludeDirs)) {
+    out_ << CSubstitutionIncludeDirs.ninja_name << " =";
     PathOutput include_path_output(
         path_output_.current_dir(),
         settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
@@ -234,31 +236,31 @@
 
   EscapeOptions opts = GetFlagOptions();
   if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) {
-    WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Tool::kToolNone,
+    WriteOneFlag(target_, &CSubstitutionAsmFlags, false, Tool::kToolNone,
                  &ConfigValues::asmflags, opts, path_output_, out_);
   }
   if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) ||
       used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
+    WriteOneFlag(target_, &CSubstitutionCFlags, false, Tool::kToolNone,
                  &ConfigValues::cflags, opts, path_output_, out_);
   }
   if (used_types.Get(SOURCE_C)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
+    WriteOneFlag(target_, &CSubstitutionCFlagsC, has_precompiled_headers,
                  CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
                  out_);
   }
   if (used_types.Get(SOURCE_CPP)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
+    WriteOneFlag(target_, &CSubstitutionCFlagsCc, has_precompiled_headers,
                  CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
                  out_);
   }
   if (used_types.Get(SOURCE_M)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
+    WriteOneFlag(target_, &CSubstitutionCFlagsObjC, has_precompiled_headers,
                  CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
                  path_output_, out_);
   }
   if (used_types.Get(SOURCE_MM)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
+    WriteOneFlag(target_, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
                  CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
                  path_output_, out_);
   }
@@ -318,14 +320,14 @@
   const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
   if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
       used_types.Get(SOURCE_C)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_C, CTool::kCToolCc,
+    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(SOURCE_CPP)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_CC, CTool::kCToolCxx,
+    WritePCHCommand(&CSubstitutionCFlagsCc, CTool::kCToolCxx,
                     tool_cxx->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
   }
@@ -333,7 +335,7 @@
   const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
   if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
       used_types.Get(SOURCE_M)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, CTool::kCToolObjC,
+    WritePCHCommand(&CSubstitutionCFlagsObjC, CTool::kCToolObjC,
                     tool_objc->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
   }
@@ -342,14 +344,14 @@
       target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
   if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
       used_types.Get(SOURCE_MM)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, CTool::kCToolObjCxx,
+    WritePCHCommand(&CSubstitutionCFlagsObjCc, CTool::kCToolObjCxx,
                     tool_objcxx->precompiled_header_type(), input_dep,
                     order_only_deps, object_files, other_files);
   }
 }
 
 void NinjaCBinaryTargetWriter::WritePCHCommand(
-    SubstitutionType flag_type,
+    const Substitution* flag_type,
     const char* tool_name,
     CTool::PrecompiledHeaderType header_type,
     const OutputFile& input_dep,
@@ -372,7 +374,7 @@
 }
 
 void NinjaCBinaryTargetWriter::WriteGCCPCHCommand(
-    SubstitutionType flag_type,
+    const Substitution* flag_type,
     const char* tool_name,
     const OutputFile& input_dep,
     const std::vector<OutputFile>& order_only_deps,
@@ -395,7 +397,7 @@
 
   // This build line needs a custom language-specific flags value. Rule-specific
   // variables are just indented underneath the rule line.
-  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
+  out_ << "  " << flag_type->ninja_name << " =";
 
   // Each substitution flag is overwritten in the target rule to replace the
   // implicitly generated -include flag with the -x <header lang> flag required
@@ -424,7 +426,7 @@
 }
 
 void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand(
-    SubstitutionType flag_type,
+    const Substitution* flag_type,
     const char* tool_name,
     const OutputFile& input_dep,
     const std::vector<OutputFile>& order_only_deps,
@@ -447,11 +449,11 @@
 
   // This build line needs a custom language-specific flags value. Rule-specific
   // variables are just indented underneath the rule line.
-  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
+  out_ << "  " << flag_type->ninja_name << " =";
 
   // Append the command to generate the .pch file.
   // This adds the value to the existing flag instead of overwriting it.
-  out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}";
+  out_ << " ${" << flag_type->ninja_name << "}";
   out_ << " /Yc" << target_->config_values().precompiled_header();
 
   // Write two blank lines to help separate the PCH build lines from the
@@ -723,11 +725,11 @@
 void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
   out_ << "  output_extension = "
        << SubstitutionWriter::GetLinkerSubstitution(
-              target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
+              target_, tool_, &CSubstitutionOutputExtension);
   out_ << std::endl;
   out_ << "  output_dir = "
        << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
-                                                    SUBSTITUTION_OUTPUT_DIR);
+                                                    &CSubstitutionOutputDir);
   out_ << std::endl;
 }
 
diff --git a/tools/gn/ninja_c_binary_target_writer.h b/tools/gn/ninja_c_binary_target_writer.h
index 4fb89d9..4d1bdfa 100644
--- a/tools/gn/ninja_c_binary_target_writer.h
+++ b/tools/gn/ninja_c_binary_target_writer.h
@@ -48,7 +48,7 @@
                         std::vector<OutputFile>* other_files);
 
   // Writes a .pch compile build line for a language type.
-  void WritePCHCommand(SubstitutionType flag_type,
+  void WritePCHCommand(const Substitution* flag_type,
                        const char* tool_name,
                        CTool::PrecompiledHeaderType header_type,
                        const OutputFile& input_dep,
@@ -56,13 +56,13 @@
                        std::vector<OutputFile>* object_files,
                        std::vector<OutputFile>* other_files);
 
-  void WriteGCCPCHCommand(SubstitutionType flag_type,
+  void WriteGCCPCHCommand(const Substitution* flag_type,
                           const char* tool_name,
                           const OutputFile& input_dep,
                           const std::vector<OutputFile>& order_only_deps,
                           std::vector<OutputFile>* gch_files);
 
-  void WriteWindowsPCHCommand(SubstitutionType flag_type,
+  void WriteWindowsPCHCommand(const Substitution* flag_type,
                               const char* tool_name,
                               const OutputFile& input_dep,
                               const std::vector<OutputFile>& order_only_deps,
diff --git a/tools/gn/ninja_target_command_util.cc b/tools/gn/ninja_target_command_util.cc
index 2aa3e4b..ef9957e 100644
--- a/tools/gn/ninja_target_command_util.cc
+++ b/tools/gn/ninja_target_command_util.cc
@@ -42,7 +42,7 @@
 }
 
 void WriteOneFlag(const Target* target,
-                  SubstitutionType subst_enum,
+                  const Substitution* subst_enum,
                   bool has_precompiled_headers,
                   const char* tool_name,
                   const std::vector<std::string>& (ConfigValues::*getter)()
@@ -51,11 +51,11 @@
                   PathOutput& path_output,
                   std::ostream& out,
                   bool write_substitution) {
-  if (!target->toolchain()->substitution_bits().used[subst_enum])
+  if (!target->toolchain()->substitution_bits().used.count(subst_enum))
     return;
 
   if (write_substitution)
-    out << kSubstitutionNinjaNames[subst_enum] << " =";
+    out << subst_enum->ninja_name << " =";
 
   if (has_precompiled_headers) {
     const CTool* tool = target->toolchain()->GetToolAsC(tool_name);
diff --git a/tools/gn/ninja_target_command_util.h b/tools/gn/ninja_target_command_util.h
index 686fada..327e120 100644
--- a/tools/gn/ninja_target_command_util.h
+++ b/tools/gn/ninja_target_command_util.h
@@ -61,7 +61,7 @@
 // tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g.
 // "defines") tool_type should be TYPE_NONE.
 void WriteOneFlag(const Target* target,
-                  SubstitutionType subst_enum,
+                  const Substitution* subst_enum,
                   bool has_precompiled_headers,
                   const char* tool_name,
                   const std::vector<std::string>& (ConfigValues::*getter)()
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 8287ec0..239e50f 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -120,11 +120,11 @@
   return rules.str();
 }
 
-void NinjaTargetWriter::WriteEscapedSubstitution(SubstitutionType type) {
+void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) {
   EscapeOptions opts;
   opts.mode = ESCAPE_NINJA;
 
-  out_ << kSubstitutionNinjaNames[type] << " = ";
+  out_ << type->ninja_name << " = ";
   EscapeStringToStream(
       out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts);
   out_ << std::endl;
@@ -134,44 +134,44 @@
   bool written_anything = false;
 
   // Target label.
-  if (bits.used[SUBSTITUTION_LABEL]) {
-    WriteEscapedSubstitution(SUBSTITUTION_LABEL);
+  if (bits.used.count(&SubstitutionLabel)) {
+    WriteEscapedSubstitution(&SubstitutionLabel);
     written_anything = true;
   }
 
   // Target label name
-  if (bits.used[SUBSTITUTION_LABEL_NAME]) {
-    WriteEscapedSubstitution(SUBSTITUTION_LABEL_NAME);
+  if (bits.used.count(&SubstitutionLabelName)) {
+    WriteEscapedSubstitution(&SubstitutionLabelName);
     written_anything = true;
   }
 
   // Root gen dir.
-  if (bits.used[SUBSTITUTION_ROOT_GEN_DIR]) {
-    WriteEscapedSubstitution(SUBSTITUTION_ROOT_GEN_DIR);
+  if (bits.used.count(&SubstitutionRootGenDir)) {
+    WriteEscapedSubstitution(&SubstitutionRootGenDir);
     written_anything = true;
   }
 
   // Root out dir.
-  if (bits.used[SUBSTITUTION_ROOT_OUT_DIR]) {
-    WriteEscapedSubstitution(SUBSTITUTION_ROOT_OUT_DIR);
+  if (bits.used.count(&SubstitutionRootOutDir)) {
+    WriteEscapedSubstitution(&SubstitutionRootOutDir);
     written_anything = true;
   }
 
   // Target gen dir.
-  if (bits.used[SUBSTITUTION_TARGET_GEN_DIR]) {
-    WriteEscapedSubstitution(SUBSTITUTION_TARGET_GEN_DIR);
+  if (bits.used.count(&SubstitutionTargetGenDir)) {
+    WriteEscapedSubstitution(&SubstitutionTargetGenDir);
     written_anything = true;
   }
 
   // Target out dir.
-  if (bits.used[SUBSTITUTION_TARGET_OUT_DIR]) {
-    WriteEscapedSubstitution(SUBSTITUTION_TARGET_OUT_DIR);
+  if (bits.used.count(&SubstitutionTargetOutDir)) {
+    WriteEscapedSubstitution(&SubstitutionTargetOutDir);
     written_anything = true;
   }
 
   // Target output name.
-  if (bits.used[SUBSTITUTION_TARGET_OUTPUT_NAME]) {
-    WriteEscapedSubstitution(SUBSTITUTION_TARGET_OUTPUT_NAME);
+  if (bits.used.count(&SubstitutionTargetOutputName)) {
+    WriteEscapedSubstitution(&SubstitutionTargetOutputName);
     written_anything = true;
   }
 
diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h
index a6db060..b49f33e 100644
--- a/tools/gn/ninja_target_writer.h
+++ b/tools/gn/ninja_target_writer.h
@@ -64,7 +64,7 @@
 
  private:
   void WriteCopyRules();
-  void WriteEscapedSubstitution(SubstitutionType type);
+  void WriteEscapedSubstitution(const Substitution* type);
 
   DISALLOW_COPY_AND_ASSIGN(NinjaTargetWriter);
 };
diff --git a/tools/gn/substitution_list.h b/tools/gn/substitution_list.h
index 45123cb..e8614eb 100644
--- a/tools/gn/substitution_list.h
+++ b/tools/gn/substitution_list.h
@@ -31,7 +31,7 @@
 
   // Returns a list of all substitution types used by the patterns in this
   // list, with the exception of LITERAL.
-  const std::vector<SubstitutionType>& required_types() const {
+  const std::vector<const Substitution*>& required_types() const {
     return required_types_;
   }
 
@@ -40,7 +40,7 @@
  private:
   std::vector<SubstitutionPattern> list_;
 
-  std::vector<SubstitutionType> required_types_;
+  std::vector<const Substitution*> required_types_;
 };
 
 #endif  // TOOLS_GN_SUBSTITUTION_LIST_H_
diff --git a/tools/gn/substitution_pattern.cc b/tools/gn/substitution_pattern.cc
index 9b5e815..b02532d 100644
--- a/tools/gn/substitution_pattern.cc
+++ b/tools/gn/substitution_pattern.cc
@@ -12,9 +12,9 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/value.h"
 
-SubstitutionPattern::Subrange::Subrange() : type(SUBSTITUTION_LITERAL) {}
+SubstitutionPattern::Subrange::Subrange() : type(&SubstitutionLiteral) {}
 
-SubstitutionPattern::Subrange::Subrange(SubstitutionType t,
+SubstitutionPattern::Subrange::Subrange(const Substitution* t,
                                         const std::string& l)
     : type(t), literal(l) {}
 
@@ -45,24 +45,25 @@
     // Pick up everything from the previous spot to here as a literal.
     if (next == std::string::npos) {
       if (cur != str.size())
-        ranges_.push_back(Subrange(SUBSTITUTION_LITERAL, str.substr(cur)));
+        ranges_.push_back(Subrange(&SubstitutionLiteral, str.substr(cur)));
       break;
     } else if (next > cur) {
       ranges_.push_back(
-          Subrange(SUBSTITUTION_LITERAL, str.substr(cur, next - cur)));
+          Subrange(&SubstitutionLiteral, str.substr(cur, next - cur)));
     }
 
     // Find which specific pattern this corresponds to.
     bool found_match = false;
-    for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES;
-         i++) {
-      const char* cur_pattern = kSubstitutionNames[i];
-      size_t cur_len = strlen(cur_pattern);
-      if (str.compare(next, cur_len, cur_pattern) == 0) {
-        ranges_.push_back(Subrange(static_cast<SubstitutionType>(i)));
-        cur = next + cur_len;
-        found_match = true;
-        break;
+    for (const SubstitutionTypes* types : AllSubstitutions) {
+      for (const Substitution* sub : *types) {
+        const char* cur_pattern = sub->name;
+        size_t cur_len = strlen(cur_pattern);
+        if (str.compare(next, cur_len, cur_pattern) == 0) {
+          ranges_.push_back(Subrange(sub));
+          cur = next + cur_len;
+          found_match = true;
+          break;
+        }
       }
     }
 
@@ -99,18 +100,18 @@
 std::string SubstitutionPattern::AsString() const {
   std::string result;
   for (const auto& elem : ranges_) {
-    if (elem.type == SUBSTITUTION_LITERAL)
+    if (elem.type == &SubstitutionLiteral)
       result.append(elem.literal);
     else
-      result.append(kSubstitutionNames[elem.type]);
+      result.append(elem.type->name);
   }
   return result;
 }
 
 void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
   for (const auto& elem : ranges_) {
-    if (elem.type != SUBSTITUTION_LITERAL)
-      bits->used[static_cast<size_t>(elem.type)] = true;
+    if (elem.type != &SubstitutionLiteral)
+      bits->used.insert(elem.type);
   }
 }
 
@@ -121,7 +122,7 @@
     return false;
   }
 
-  if (ranges_[0].type == SUBSTITUTION_LITERAL) {
+  if (ranges_[0].type == &SubstitutionLiteral) {
     // If the first thing is a literal, it must start with the output dir.
     if (!EnsureStringIsInOutputDir(build_settings->build_dir(),
                                    ranges_[0].literal, origin_, err))
diff --git a/tools/gn/substitution_pattern.h b/tools/gn/substitution_pattern.h
index 850d736..81bd897 100644
--- a/tools/gn/substitution_pattern.h
+++ b/tools/gn/substitution_pattern.h
@@ -20,14 +20,14 @@
  public:
   struct Subrange {
     Subrange();
-    explicit Subrange(SubstitutionType t, const std::string& l = std::string());
+    explicit Subrange(const Substitution* t, const std::string& l = std::string());
     ~Subrange();
 
     inline bool operator==(const Subrange& other) const {
       return type == other.type && literal == other.literal;
     }
 
-    SubstitutionType type;
+    const Substitution* type;
 
     // When type_ == LITERAL, this specifies the literal.
     std::string literal;
@@ -50,7 +50,7 @@
   std::string AsString() const;
 
   // Sets the bits in the given vector corresponding to the substitutions used
-  // by this pattern. SUBSTITUTION_LITERAL is ignored.
+  // by this pattern. SubstitutionLiteral is ignored.
   void FillRequiredTypes(SubstitutionBits* bits) const;
 
   // Checks whether this pattern resolves to something in the output directory
@@ -59,8 +59,8 @@
   bool IsInOutputDir(const BuildSettings* build_settings, Err* err) const;
 
   // Returns a vector listing the substitutions used by this pattern, not
-  // counting SUBSTITUTION_LITERAL.
-  const std::vector<SubstitutionType>& required_types() const {
+  // counting SubstitutionLiteral.
+  const std::vector<const Substitution*>& required_types() const {
     return required_types_;
   }
 
@@ -73,7 +73,7 @@
   std::vector<Subrange> ranges_;
   const ParseNode* origin_;
 
-  std::vector<SubstitutionType> required_types_;
+  std::vector<const Substitution*> required_types_;
 };
 
 #endif  // TOOLS_GN_SUBSTITUTION_PATTERN_H_
diff --git a/tools/gn/substitution_pattern_unittest.cc b/tools/gn/substitution_pattern_unittest.cc
index c8c7396..59c057f 100644
--- a/tools/gn/substitution_pattern_unittest.cc
+++ b/tools/gn/substitution_pattern_unittest.cc
@@ -12,7 +12,7 @@
   EXPECT_TRUE(pattern.Parse("This is a literal", nullptr, &err));
   EXPECT_FALSE(err.has_error());
   ASSERT_EQ(1u, pattern.ranges().size());
-  EXPECT_EQ(SUBSTITUTION_LITERAL, pattern.ranges()[0].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[0].type);
   EXPECT_EQ("This is a literal", pattern.ranges()[0].literal);
 }
 
@@ -24,13 +24,13 @@
   EXPECT_FALSE(err.has_error());
   ASSERT_EQ(5u, pattern.ranges().size());
 
-  EXPECT_EQ(SUBSTITUTION_LITERAL, pattern.ranges()[0].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[0].type);
   EXPECT_EQ("AA", pattern.ranges()[0].literal);
-  EXPECT_EQ(SUBSTITUTION_SOURCE, pattern.ranges()[1].type);
-  EXPECT_EQ(SUBSTITUTION_SOURCE_NAME_PART, pattern.ranges()[2].type);
-  EXPECT_EQ(SUBSTITUTION_LITERAL, pattern.ranges()[3].type);
+  EXPECT_EQ(&SubstitutionSource, pattern.ranges()[1].type);
+  EXPECT_EQ(&SubstitutionSourceNamePart, pattern.ranges()[2].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[3].type);
   EXPECT_EQ("BB", pattern.ranges()[3].literal);
-  EXPECT_EQ(SUBSTITUTION_SOURCE_FILE_PART, pattern.ranges()[4].type);
+  EXPECT_EQ(&SubstitutionSourceFilePart, pattern.ranges()[4].type);
 }
 
 TEST(SubstitutionPattern, ParseErrors) {
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index d47a8d0..f38013e 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -7,234 +7,178 @@
 #include <stddef.h>
 #include <stdlib.h>
 
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/err.h"
 
-const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES] = {
-    "<<literal>>",  // SUBSTITUTION_LITERAL
+const std::vector<SubstitutionTypes*> AllSubstitutions = {&GeneralSubstitutions,
+                                                          &CSubstitutions};
 
-    "{{source}}",  // SUBSTITUTION_SOURCE
-    "{{output}}",  // SUBSTITUTION_OUTPUT
+const SubstitutionTypes GeneralSubstitutions = {
+    &SubstitutionLiteral,
 
-    "{{source_name_part}}",          // SUBSTITUTION_NAME_PART
-    "{{source_file_part}}",          // SUBSTITUTION_FILE_PART
-    "{{source_dir}}",                // SUBSTITUTION_SOURCE_DIR
-    "{{source_root_relative_dir}}",  // SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR
-    "{{source_gen_dir}}",            // SUBSTITUTION_SOURCE_GEN_DIR
-    "{{source_out_dir}}",            // SUBSTITUTION_SOURCE_OUT_DIR
-    "{{source_target_relative}}",    // SUBSTITUTION_SOURCE_TARGET_RELATIVE
+    &SubstitutionOutput,
+    &SubstitutionLabel,
+    &SubstitutionLabelName,
+    &SubstitutionRootGenDir,
+    &SubstitutionRootOutDir,
+    &SubstitutionTargetGenDir,
+    &SubstitutionTargetOutDir,
+    &SubstitutionTargetOutputName,
 
-    "{{label}}",               // SUBSTITUTION_LABEL
-    "{{label_name}}",          // SUBSTITUTION_LABEL_NAME
-    "{{root_gen_dir}}",        // SUBSTITUTION_ROOT_GEN_DIR
-    "{{root_out_dir}}",        // SUBSTITUTION_ROOT_OUT_DIR
-    "{{target_gen_dir}}",      // SUBSTITUTION_TARGET_GEN_DIR
-    "{{target_out_dir}}",      // SUBSTITUTION_TARGET_OUT_DIR
-    "{{target_output_name}}",  // SUBSTITUTION_TARGET_OUTPUT_NAME
+    &SubstitutionSource,
+    &SubstitutionSourceNamePart,
+    &SubstitutionSourceFilePart,
+    &SubstitutionSourceDir,
+    &SubstitutionSourceRootRelativeDir,
+    &SubstitutionSourceGenDir,
+    &SubstitutionSourceOutDir,
+    &SubstitutionSourceTargetRelative,
 
-    "{{asmflags}}",      // SUBSTITUTION_ASMFLAGS
-    "{{cflags}}",        // SUBSTITUTION_CFLAGS
-    "{{cflags_c}}",      // SUBSTITUTION_CFLAGS_C
-    "{{cflags_cc}}",     // SUBSTITUTION_CFLAGS_CC
-    "{{cflags_objc}}",   // SUBSTITUTION_CFLAGS_OBJC
-    "{{cflags_objcc}}",  // SUBSTITUTION_CFLAGS_OBJCC
-    "{{defines}}",       // SUBSTITUTION_DEFINES
-    "{{include_dirs}}",  // SUBSTITUTION_INCLUDE_DIRS
+    &SubstitutionBundleRootDir,
+    &SubstitutionBundleContentsDir,
+    &SubstitutionBundleResourcesDir,
+    &SubstitutionBundleExecutableDir,
 
-    "{{inputs}}",            // SUBSTITUTION_LINKER_INPUTS
-    "{{inputs_newline}}",    // SUBSTITUTION_LINKER_INPUTS_NEWLINE
-    "{{ldflags}}",           // SUBSTITUTION_LDFLAGS
-    "{{libs}}",              // SUBSTITUTION_LIBS
-    "{{output_dir}}",        // SUBSTITUTION_OUTPUT_DIR
-    "{{output_extension}}",  // SUBSTITUTION_OUTPUT_EXTENSION
-    "{{solibs}}",            // SUBSTITUTION_SOLIBS
+    &SubstitutionBundleProductType,
+    &SubstitutionBundlePartialInfoPlist,
 
-    "{{arflags}}",  // SUBSTITUTION_ARFLAGS
-
-    "{{bundle_root_dir}}",            // SUBSTITUTION_BUNDLE_ROOT_DIR
-    "{{bundle_contents_dir}}",        // SUBSTITUTION_BUNDLE_CONTENTS_DIR
-    "{{bundle_resources_dir}}",       // SUBSTITUTION_BUNDLE_RESOURCES_DIR
-    "{{bundle_executable_dir}}",      // SUBSTITUTION_BUNDLE_EXECUTABLE_DIR
-    "{{bundle_product_type}}",        // SUBSTITUTION_BUNDLE_PRODUCT_TYPE
-    "{{bundle_partial_info_plist}}",  // SUBSTITUTION_BUNDLE_PARTIAL_INFO_PLIST,
-
-    "{{response_file_name}}",  // SUBSTITUTION_RSP_FILE_NAME
+    &SubstitutionRspFileName,
 };
 
-const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES] = {
-    nullptr,  // SUBSTITUTION_LITERAL
+const Substitution SubstitutionLiteral = {"<<literal>>", nullptr};
 
-    "in",   // SUBSTITUTION_SOURCE
-    "out",  // SUBSTITUTION_OUTPUT
+const Substitution SubstitutionSource = {"{{source}}", "in"};
+const Substitution SubstitutionOutput = {"{{output}}", "out"};
 
-    "source_name_part",          // SUBSTITUTION_NAME_PART
-    "source_file_part",          // SUBSTITUTION_FILE_PART
-    "source_dir",                // SUBSTITUTION_SOURCE_DIR
-    "source_root_relative_dir",  // SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR
-    "source_gen_dir",            // SUBSTITUTION_SOURCE_GEN_DIR
-    "source_out_dir",            // SUBSTITUTION_SOURCE_OUT_DIR
-    "source_target_relative",    // SUBSTITUTION_SOURCE_TARGET_RELATIVE
+const Substitution SubstitutionSourceNamePart = {"{{source_name_part}}",
+                                                 "source_name_part"};
+const Substitution SubstitutionSourceFilePart = {"{{source_file_part}}",
+                                                 "source_file_part"};
+const Substitution SubstitutionSourceDir = {"{{source_dir}}", "source_dir"};
+const Substitution SubstitutionSourceRootRelativeDir = {
+    "{{source_root_relative_dir}}", "source_root_relative_dir"};
+const Substitution SubstitutionSourceGenDir = {"{{source_gen_dir}}",
+                                               "source_gen_dir"};
+const Substitution SubstitutionSourceOutDir = {"{{source_out_dir}}",
+                                               "source_out_dir"};
+const Substitution SubstitutionSourceTargetRelative = {
+    "{{source_target_relative}}", "source_target_relative"};
 
-    "label",               // SUBSTITUTION_LABEL
-    "label_name",          // SUBSTITUTION_LABEL_NAME
-    "root_gen_dir",        // SUBSTITUTION_ROOT_GEN_DIR
-    "root_out_dir",        // SUBSTITUTION_ROOT_OUT_DIR
-    "target_gen_dir",      // SUBSTITUTION_TARGET_GEN_DIR
-    "target_out_dir",      // SUBSTITUTION_TARGET_OUT_DIR
-    "target_output_name",  // SUBSTITUTION_TARGET_OUTPUT_NAME
+// Valid for all compiler and linker tools. These depend on the target and
+// do not vary on a per-file basis.
+const Substitution SubstitutionLabel = {"{{label}}", "label"};
+const Substitution SubstitutionLabelName = {"{{label_name}}", "label_name"};
+const Substitution SubstitutionRootGenDir = {"{{root_gen_dir}}",
+                                             "root_gen_dir"};
+const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
+                                             "root_out_dir"};
+const Substitution SubstitutionTargetGenDir = {"{{target_gen_dir}}",
+                                               "target_gen_dir"};
+const Substitution SubstitutionTargetOutDir = {"{{target_out_dir}}",
+                                               "target_out_dir"};
+const Substitution SubstitutionTargetOutputName = {"{{target_output_name}}",
+                                                   "target_output_name"};
 
-    "asmflags",      // SUBSTITUTION_ASMFLAGS
-    "cflags",        // SUBSTITUTION_CFLAGS
-    "cflags_c",      // SUBSTITUTION_CFLAGS_C
-    "cflags_cc",     // SUBSTITUTION_CFLAGS_CC
-    "cflags_objc",   // SUBSTITUTION_CFLAGS_OBJC
-    "cflags_objcc",  // SUBSTITUTION_CFLAGS_OBJCC
-    "defines",       // SUBSTITUTION_DEFINES
-    "include_dirs",  // SUBSTITUTION_INCLUDE_DIRS
+// Valid for bundle_data targets.
+const Substitution SubstitutionBundleRootDir = {"{{bundle_root_dir}}",
+                                                "bundle_root_dir"};
+const Substitution SubstitutionBundleContentsDir = {"{{bundle_contents_dir}}",
+                                                    "bundle_contents_dir"};
+const Substitution SubstitutionBundleResourcesDir = {"{{bundle_resources_dir}}",
+                                                     "bundle_resources_dir"};
+const Substitution SubstitutionBundleExecutableDir = {
+    "{{bundle_executable_dir}}", "bundle_executable_dir"};
 
-    // LINKER_INPUTS expands to the same Ninja var as SUBSTITUTION_SOURCE. These
-    // are used in different contexts and are named differently to keep things
-    // clear, but they both expand to the "set of input files" for a build rule.
-    "in",                // SUBSTITUTION_LINKER_INPUTS
-    "in_newline",        // SUBSTITUTION_LINKER_INPUTS_NEWLINE
-    "ldflags",           // SUBSTITUTION_LDFLAGS
-    "libs",              // SUBSTITUTION_LIBS
-    "output_dir",        // SUBSTITUTION_OUTPUT_DIR
-    "output_extension",  // SUBSTITUTION_OUTPUT_EXTENSION
-    "solibs",            // SUBSTITUTION_SOLIBS
+// Valid for compile_xcassets tool.
+const Substitution SubstitutionBundleProductType = {"{{bundle_product_type}}",
+                                                    "product_type"};
+const Substitution SubstitutionBundlePartialInfoPlist = {
+    "{{bundle_partial_info_plist}}", "partial_info_plist"};
 
-    "arflags",  // SUBSTITUTION_ARFLAGS
+// Used only for the args of actions.
+const Substitution SubstitutionRspFileName = {"{{response_file_name}}",
+                                              "rspfile"};
 
-    "bundle_root_dir",        // SUBSTITUTION_BUNDLE_ROOT_DIR
-    "bundle_contents_dir",    // SUBSTITUTION_BUNDLE_CONTENTS_DIR
-    "bundle_resources_dir",   // SUBSTITUTION_BUNDLE_RESOURCES_DIR
-    "bundle_executable_dir",  // SUBSTITUTION_BUNDLE_EXECUTABLE_DIR
-    "product_type",           // SUBSTITUTION_BUNDLE_PRODUCT_TYPE
-    "partial_info_plist",     // SUBSTITUTION_BUNDLE_PARTIAL_INFO_PLIST
-
-    "rspfile",  // SUBSTITUTION_RSP_FILE_NAME
-};
-
-SubstitutionBits::SubstitutionBits() : used() {}
+SubstitutionBits::SubstitutionBits() = default;
 
 void SubstitutionBits::MergeFrom(const SubstitutionBits& other) {
-  for (size_t i = 0; i < SUBSTITUTION_NUM_TYPES; i++)
-    used[i] |= other.used[i];
+  for (const Substitution* s : other.used)
+    used.insert(s);
 }
 
-void SubstitutionBits::FillVector(std::vector<SubstitutionType>* vect) const {
-  for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES; i++) {
-    if (used[i])
-      vect->push_back(static_cast<SubstitutionType>(i));
+void SubstitutionBits::FillVector(
+    std::vector<const Substitution*>* vect) const {
+  for (const Substitution* s : used) {
+    vect->push_back(s);
   }
 }
 
-bool SubstitutionIsInOutputDir(SubstitutionType type) {
-  return type == SUBSTITUTION_SOURCE_GEN_DIR ||
-         type == SUBSTITUTION_SOURCE_OUT_DIR ||
-         type == SUBSTITUTION_ROOT_GEN_DIR ||
-         type == SUBSTITUTION_ROOT_OUT_DIR ||
-         type == SUBSTITUTION_TARGET_GEN_DIR ||
-         type == SUBSTITUTION_TARGET_OUT_DIR;
+bool SubstitutionIsInOutputDir(const Substitution* type) {
+  return type == &SubstitutionSourceGenDir ||
+         type == &SubstitutionSourceOutDir || type == &SubstitutionRootGenDir ||
+         type == &SubstitutionRootOutDir || type == &SubstitutionTargetGenDir ||
+         type == &SubstitutionTargetOutDir;
 }
 
-bool SubstitutionIsInBundleDir(SubstitutionType type) {
-  return type == SUBSTITUTION_BUNDLE_ROOT_DIR ||
-         type == SUBSTITUTION_BUNDLE_CONTENTS_DIR ||
-         type == SUBSTITUTION_BUNDLE_RESOURCES_DIR ||
-         type == SUBSTITUTION_BUNDLE_EXECUTABLE_DIR;
+bool SubstitutionIsInBundleDir(const Substitution* type) {
+  return type == &SubstitutionBundleRootDir ||
+         type == &SubstitutionBundleContentsDir ||
+         type == &SubstitutionBundleResourcesDir ||
+         type == &SubstitutionBundleExecutableDir;
 }
 
-bool IsValidBundleDataSubstitution(SubstitutionType type) {
-  return type == SUBSTITUTION_LITERAL ||
-         type == SUBSTITUTION_SOURCE_NAME_PART ||
-         type == SUBSTITUTION_SOURCE_FILE_PART ||
-         type == SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR ||
-         type == SUBSTITUTION_BUNDLE_ROOT_DIR ||
-         type == SUBSTITUTION_BUNDLE_CONTENTS_DIR ||
-         type == SUBSTITUTION_BUNDLE_RESOURCES_DIR ||
-         type == SUBSTITUTION_BUNDLE_EXECUTABLE_DIR;
+bool IsValidBundleDataSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral || type == &SubstitutionSourceNamePart ||
+         type == &SubstitutionSourceFilePart ||
+         type == &SubstitutionSourceRootRelativeDir ||
+         type == &SubstitutionBundleRootDir ||
+         type == &SubstitutionBundleContentsDir ||
+         type == &SubstitutionBundleResourcesDir ||
+         type == &SubstitutionBundleExecutableDir;
 }
 
-bool IsValidSourceSubstitution(SubstitutionType type) {
-  return type == SUBSTITUTION_LITERAL || type == SUBSTITUTION_SOURCE ||
-         type == SUBSTITUTION_SOURCE_NAME_PART ||
-         type == SUBSTITUTION_SOURCE_FILE_PART ||
-         type == SUBSTITUTION_SOURCE_DIR ||
-         type == SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR ||
-         type == SUBSTITUTION_SOURCE_GEN_DIR ||
-         type == SUBSTITUTION_SOURCE_OUT_DIR ||
-         type == SUBSTITUTION_SOURCE_TARGET_RELATIVE;
+bool IsValidSourceSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral || type == &SubstitutionSource ||
+         type == &SubstitutionSourceNamePart ||
+         type == &SubstitutionSourceFilePart ||
+         type == &SubstitutionSourceDir ||
+         type == &SubstitutionSourceRootRelativeDir ||
+         type == &SubstitutionSourceGenDir ||
+         type == &SubstitutionSourceOutDir ||
+         type == &SubstitutionSourceTargetRelative;
 }
 
-bool IsValidScriptArgsSubstitution(SubstitutionType type) {
-  return IsValidSourceSubstitution(type) || type == SUBSTITUTION_RSP_FILE_NAME;
+bool IsValidScriptArgsSubstitution(const Substitution* type) {
+  return IsValidSourceSubstitution(type) || type == &SubstitutionRspFileName;
 }
 
-bool IsValidToolSubstitution(SubstitutionType type) {
-  return type == SUBSTITUTION_LITERAL || type == SUBSTITUTION_OUTPUT ||
-         type == SUBSTITUTION_LABEL || type == SUBSTITUTION_LABEL_NAME ||
-         type == SUBSTITUTION_ROOT_GEN_DIR ||
-         type == SUBSTITUTION_ROOT_OUT_DIR ||
-         type == SUBSTITUTION_TARGET_GEN_DIR ||
-         type == SUBSTITUTION_TARGET_OUT_DIR ||
-         type == SUBSTITUTION_TARGET_OUTPUT_NAME;
+bool IsValidToolSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral || type == &SubstitutionOutput ||
+         type == &SubstitutionLabel || type == &SubstitutionLabelName ||
+         type == &SubstitutionRootGenDir || type == &SubstitutionRootOutDir ||
+         type == &SubstitutionTargetGenDir ||
+         type == &SubstitutionTargetOutDir ||
+         type == &SubstitutionTargetOutputName;
 }
 
-bool IsValidCompilerSubstitution(SubstitutionType type) {
-  return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
-         type == SUBSTITUTION_SOURCE || type == SUBSTITUTION_ASMFLAGS ||
-         type == SUBSTITUTION_CFLAGS || type == SUBSTITUTION_CFLAGS_C ||
-         type == SUBSTITUTION_CFLAGS_CC || type == SUBSTITUTION_CFLAGS_OBJC ||
-         type == SUBSTITUTION_CFLAGS_OBJCC || type == SUBSTITUTION_DEFINES ||
-         type == SUBSTITUTION_INCLUDE_DIRS;
+bool IsValidCopySubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &SubstitutionSource;
 }
 
-bool IsValidCompilerOutputsSubstitution(SubstitutionType type) {
-  // All tool types except "output" (which would be infinitely recursive).
-  return (IsValidToolSubstitution(type) && type != SUBSTITUTION_OUTPUT) ||
-         IsValidSourceSubstitution(type);
+bool IsValidCompileXCassetsSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+         type == &SubstitutionBundleProductType ||
+         type == &SubstitutionBundlePartialInfoPlist;
 }
 
-bool IsValidLinkerSubstitution(SubstitutionType type) {
-  return IsValidToolSubstitution(type) || type == SUBSTITUTION_LINKER_INPUTS ||
-         type == SUBSTITUTION_LINKER_INPUTS_NEWLINE ||
-         type == SUBSTITUTION_LDFLAGS || type == SUBSTITUTION_LIBS ||
-         type == SUBSTITUTION_OUTPUT_DIR ||
-         type == SUBSTITUTION_OUTPUT_EXTENSION || type == SUBSTITUTION_SOLIBS;
-}
-
-bool IsValidLinkerOutputsSubstitution(SubstitutionType type) {
-  // All valid compiler outputs plus the output extension.
-  return IsValidCompilerOutputsSubstitution(type) ||
-         type == SUBSTITUTION_OUTPUT_DIR ||
-         type == SUBSTITUTION_OUTPUT_EXTENSION;
-}
-
-bool IsValidALinkSubstitution(SubstitutionType type) {
-  return IsValidToolSubstitution(type) || type == SUBSTITUTION_LINKER_INPUTS ||
-         type == SUBSTITUTION_LINKER_INPUTS_NEWLINE ||
-         type == SUBSTITUTION_ARFLAGS || type == SUBSTITUTION_OUTPUT_DIR ||
-         type == SUBSTITUTION_OUTPUT_EXTENSION;
-}
-
-bool IsValidCopySubstitution(SubstitutionType type) {
-  return IsValidToolSubstitution(type) || type == SUBSTITUTION_SOURCE;
-}
-
-bool IsValidCompileXCassetsSubstitution(SubstitutionType type) {
-  return IsValidToolSubstitution(type) || type == SUBSTITUTION_LINKER_INPUTS ||
-         type == SUBSTITUTION_BUNDLE_PRODUCT_TYPE ||
-         type == SUBSTITUTION_BUNDLE_PARTIAL_INFO_PLIST;
-}
-
-bool EnsureValidSubstitutions(const std::vector<SubstitutionType>& types,
-                              bool (*is_valid_subst)(SubstitutionType),
+bool EnsureValidSubstitutions(const std::vector<const Substitution*>& types,
+                              bool (*is_valid_subst)(const Substitution*),
                               const ParseNode* origin,
                               Err* err) {
-  for (SubstitutionType type : types) {
+  for (const Substitution* type : types) {
     if (!is_valid_subst(type)) {
       *err = Err(origin, "Invalid substitution type.",
-                 "The substitution " + std::string(kSubstitutionNames[type]) +
+                 "The substitution " + std::string(type->name) +
                      " isn't valid for something\n"
                      "operating on a source file such as this.");
       return false;
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 9e2bdb7..5bdbf90 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -7,87 +7,64 @@
 
 #include <vector>
 
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+
 class Err;
 class ParseNode;
 
-// Keep kSubstitutionNames, kSubstitutionNinjaNames and the
-// IsValid*Substitution functions in sync if you change anything here.
-enum SubstitutionType {
-  SUBSTITUTION_LITERAL = 0,
-
-  // The index of the first pattern. To loop overal all patterns, go from here
-  // until NUM_TYPES.
-  SUBSTITUTION_FIRST_PATTERN,
-
-  // These map to Ninja's {in} and {out} variables.
-  SUBSTITUTION_SOURCE = SUBSTITUTION_FIRST_PATTERN,  // {{source}}
-  SUBSTITUTION_OUTPUT,                               // {{output}}
-
-  // Valid for all compiler tools.
-  SUBSTITUTION_SOURCE_NAME_PART,          // {{source_name_part}}
-  SUBSTITUTION_SOURCE_FILE_PART,          // {{source_file_part}}
-  SUBSTITUTION_SOURCE_DIR,                // {{source_dir}}
-  SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR,  // {{root_relative_dir}}
-  SUBSTITUTION_SOURCE_GEN_DIR,            // {{source_gen_dir}}
-  SUBSTITUTION_SOURCE_OUT_DIR,            // {{source_out_dir}}
-  SUBSTITUTION_SOURCE_TARGET_RELATIVE,    // {{source_target_relative}}
-
-  // Valid for all compiler and linker tools. These depend on the target and
-  // do not vary on a per-file basis.
-  SUBSTITUTION_LABEL,               // {{label}}
-  SUBSTITUTION_LABEL_NAME,          // {{label_name}}
-  SUBSTITUTION_ROOT_GEN_DIR,        // {{root_gen_dir}}
-  SUBSTITUTION_ROOT_OUT_DIR,        // {{root_out_dir}}
-  SUBSTITUTION_TARGET_GEN_DIR,      // {{target_gen_dir}}
-  SUBSTITUTION_TARGET_OUT_DIR,      // {{target_out_dir}}
-  SUBSTITUTION_TARGET_OUTPUT_NAME,  // {{target_output_name}}
-
-  // Valid for compiler tools.
-  SUBSTITUTION_ASMFLAGS,      // {{asmflags}}
-  SUBSTITUTION_CFLAGS,        // {{cflags}}
-  SUBSTITUTION_CFLAGS_C,      // {{cflags_c}}
-  SUBSTITUTION_CFLAGS_CC,     // {{cflags_cc}}
-  SUBSTITUTION_CFLAGS_OBJC,   // {{cflags_objc}}
-  SUBSTITUTION_CFLAGS_OBJCC,  // {{cflags_objcc}}
-  SUBSTITUTION_DEFINES,       // {{defines}}
-  SUBSTITUTION_INCLUDE_DIRS,  // {{include_dirs}}
-
-  // Valid for linker tools.
-  SUBSTITUTION_LINKER_INPUTS,          // {{inputs}}
-  SUBSTITUTION_LINKER_INPUTS_NEWLINE,  // {{inputs_newline}}
-  SUBSTITUTION_LDFLAGS,                // {{ldflags}}
-  SUBSTITUTION_LIBS,                   // {{libs}}
-  SUBSTITUTION_OUTPUT_DIR,             // {{output_dir}}
-  SUBSTITUTION_OUTPUT_EXTENSION,       // {{output_extension}}
-  SUBSTITUTION_SOLIBS,                 // {{solibs}}
-
-  // Valid for alink only.
-  SUBSTITUTION_ARFLAGS,  // {{arflags}}
-
-  // Valid for bundle_data targets.
-  SUBSTITUTION_BUNDLE_ROOT_DIR,        // {{bundle_root_dir}}
-  SUBSTITUTION_BUNDLE_CONTENTS_DIR,    // {{bundle_contents_dir}}
-  SUBSTITUTION_BUNDLE_RESOURCES_DIR,   // {{bundle_resources_dir}}
-  SUBSTITUTION_BUNDLE_EXECUTABLE_DIR,  // {{bundle_executable_dir}}
-
-  // Valid for compile_xcassets tool.
-  SUBSTITUTION_BUNDLE_PRODUCT_TYPE,        // {{bundle_product_type}}
-  SUBSTITUTION_BUNDLE_PARTIAL_INFO_PLIST,  // {{bundle_partial_info_plist}}
-
-  // Used only for the args of actions.
-  SUBSTITUTION_RSP_FILE_NAME,  // {{response_file_name}}
-
-  SUBSTITUTION_NUM_TYPES  // Must be last.
+// Each pair here represents the string representation of the substitution in GN
+// and in Ninja.
+struct Substitution {
+  const char* name;
+  const char* ninja_name;
+  DISALLOW_COPY_AND_ASSIGN(Substitution);
 };
 
-// An array of size SUBSTITUTION_NUM_TYPES that lists the names of the
-// substitution patterns, including the curly braces. So, for example,
-// kSubstitutionNames[SUBSTITUTION_SOURCE] == "{{source}}".
-extern const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES];
+using SubstitutionTypes = const std::vector<const Substitution*>;
 
-// Ninja variables corresponding to each substitution. These do not include
-// the dollar sign.
-extern const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES];
+// All possible substitutions, organized into logical sets.
+extern const std::vector<SubstitutionTypes*> AllSubstitutions;
+
+// The set of substitutions available to all tools.
+extern const SubstitutionTypes GeneralSubstitutions;
+
+// Types of substitutions.
+extern const Substitution SubstitutionLiteral;
+
+// Valid for all tools. These depend on the target and
+// do not vary on a per-file basis.
+extern const Substitution SubstitutionOutput;
+extern const Substitution SubstitutionLabel;
+extern const Substitution SubstitutionLabelName;
+extern const Substitution SubstitutionRootGenDir;
+extern const Substitution SubstitutionRootOutDir;
+extern const Substitution SubstitutionTargetGenDir;
+extern const Substitution SubstitutionTargetOutDir;
+extern const Substitution SubstitutionTargetOutputName;
+
+// Valid for all compiler tools.
+extern const Substitution SubstitutionSource;
+extern const Substitution SubstitutionSourceNamePart;
+extern const Substitution SubstitutionSourceFilePart;
+extern const Substitution SubstitutionSourceDir;
+extern const Substitution SubstitutionSourceRootRelativeDir;
+extern const Substitution SubstitutionSourceGenDir;
+extern const Substitution SubstitutionSourceOutDir;
+extern const Substitution SubstitutionSourceTargetRelative;
+
+// Valid for bundle_data targets.
+extern const Substitution SubstitutionBundleRootDir;
+extern const Substitution SubstitutionBundleContentsDir;
+extern const Substitution SubstitutionBundleResourcesDir;
+extern const Substitution SubstitutionBundleExecutableDir;
+
+// Valid for compile_xcassets tool.
+extern const Substitution SubstitutionBundleProductType;
+extern const Substitution SubstitutionBundlePartialInfoPlist;
+
+// Used only for the args of actions.
+extern const Substitution SubstitutionRspFileName;
 
 // A wrapper around an array if flags indicating whether a given substitution
 // type is required in some context. By convention, the LITERAL type bit is
@@ -99,43 +76,40 @@
   // then be the union of all bits in the two lists.
   void MergeFrom(const SubstitutionBits& other);
 
-  // Converts the substitution type bitfield (with a true set for each required
-  // item) to a vector of the types listed. Does not include LITERAL.
-  void FillVector(std::vector<SubstitutionType>* vect) const;
+  // Converts the substitution type set to a vector of the types listed. Does
+  // not include SubstitutionLiteral.
+  void FillVector(std::vector<const Substitution*>* vect) const;
 
-  bool used[SUBSTITUTION_NUM_TYPES];
+  // This set depends on global uniqueness of pointers, and so all points in
+  // this set should be the Substitution* constants.
+  base::flat_set<const Substitution*> used;
 };
 
 // Returns true if the given substitution pattern references the output
 // directory. This is used to check strings that begin with a substitution to
 // verify that they produce a file in the output directory.
-bool SubstitutionIsInOutputDir(SubstitutionType type);
+bool SubstitutionIsInOutputDir(const Substitution* type);
 
 // Returns true if the given substitution pattern references the bundle
 // directory. This is used to check strings that begin with a substitution to
 // verify that they produce a file in the bundle directory.
-bool SubstitutionIsInBundleDir(SubstitutionType type);
+bool SubstitutionIsInBundleDir(const Substitution* type);
 
 // Returns true if the given substitution is valid for the named purpose.
-bool IsValidBundleDataSubstitution(SubstitutionType type);
-bool IsValidSourceSubstitution(SubstitutionType type);
-bool IsValidScriptArgsSubstitution(SubstitutionType type);
+bool IsValidBundleDataSubstitution(const Substitution* type);
+bool IsValidSourceSubstitution(const Substitution* type);
+bool IsValidScriptArgsSubstitution(const Substitution* type);
 
 // Both compiler and linker tools.
-bool IsValidToolSubstitution(SubstitutionType type);
-bool IsValidCompilerSubstitution(SubstitutionType type);
-bool IsValidCompilerOutputsSubstitution(SubstitutionType type);
-bool IsValidLinkerSubstitution(SubstitutionType type);
-bool IsValidLinkerOutputsSubstitution(SubstitutionType type);
-bool IsValidALinkSubstitution(SubstitutionType type);
-bool IsValidCopySubstitution(SubstitutionType type);
-bool IsValidCompileXCassetsSubstitution(SubstitutionType type);
+bool IsValidToolSubstitution(const Substitution* type);
+bool IsValidCopySubstitution(const Substitution* type);
+bool IsValidCompileXCassetsSubstitution(const Substitution* type);
 
 // Validates that each substitution type in the vector passes the given
 // is_valid_subst predicate. Returns true on success. On failure, fills in the
 // error object with an appropriate message and returns false.
-bool EnsureValidSubstitutions(const std::vector<SubstitutionType>& types,
-                              bool (*is_valid_subst)(SubstitutionType),
+bool EnsureValidSubstitutions(const std::vector<const Substitution*>& types,
+                              bool (*is_valid_subst)(const Substitution*),
                               const ParseNode* origin,
                               Err* err);
 
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 96a12b1..c8bc70f 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -5,6 +5,7 @@
 #include "tools/gn/substitution_writer.h"
 
 #include "tools/gn/build_settings.h"
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/escape.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/output_file.h"
@@ -156,11 +157,11 @@
   bool needs_quotes = false;
   std::string result;
   for (const auto& range : pattern.ranges()) {
-    if (range.type == SUBSTITUTION_LITERAL) {
+    if (range.type == &SubstitutionLiteral) {
       result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
     } else {
       result.append("${");
-      result.append(kSubstitutionNinjaNames[range.type]);
+      result.append(range.type->ninja_name);
       result.append("}");
     }
   }
@@ -176,7 +177,7 @@
                                               std::vector<SourceFile>* output) {
   for (const auto& pattern : list.list()) {
     CHECK(pattern.ranges().size() == 1 &&
-          pattern.ranges()[0].type == SUBSTITUTION_LITERAL)
+          pattern.ranges()[0].type == &SubstitutionLiteral)
         << "The substitution pattern \"" << pattern.AsString()
         << "\" was expected to be a literal with no {{substitutions}}.";
     const std::string& literal = pattern.ranges()[0].literal;
@@ -219,7 +220,7 @@
     const SourceFile& source) {
   std::string result_value;
   for (const auto& subrange : pattern.ranges()) {
-    if (subrange.type == SUBSTITUTION_LITERAL) {
+    if (subrange.type == &SubstitutionLiteral) {
       result_value.append(subrange.literal);
     } else {
       result_value.append(GetSourceSubstitution(target, settings, source,
@@ -316,7 +317,7 @@
     const Target* target,
     const Settings* settings,
     const SourceFile& source,
-    const std::vector<SubstitutionType>& types,
+    const std::vector<const Substitution*>& types,
     const EscapeOptions& escape_options,
     std::ostream& out) {
   for (const auto& type : types) {
@@ -324,8 +325,8 @@
     // is implicit in the rule. RESPONSE_FILE_NAME is written separately
     // only when writing target rules since it can never be used in any
     // other context (like process_file_template).
-    if (type != SUBSTITUTION_SOURCE && type != SUBSTITUTION_RSP_FILE_NAME) {
-      out << "  " << kSubstitutionNinjaNames[type] << " = ";
+    if (type != &SubstitutionSource && type != &SubstitutionRspFileName) {
+      out << "  " << type->ninja_name << " = ";
       EscapeStringToStream(
           out,
           GetSourceSubstitution(target, settings, source, type, OUTPUT_RELATIVE,
@@ -341,59 +342,46 @@
     const Target* target,
     const Settings* settings,
     const SourceFile& source,
-    SubstitutionType type,
+    const Substitution* type,
     OutputStyle output_style,
     const SourceDir& relative_to) {
   std::string to_rebase;
-  switch (type) {
-    case SUBSTITUTION_SOURCE:
-      if (source.is_system_absolute())
-        return source.value();
-      to_rebase = source.value();
-      break;
-
-    case SUBSTITUTION_SOURCE_NAME_PART:
-      return FindFilenameNoExtension(&source.value()).as_string();
-
-    case SUBSTITUTION_SOURCE_FILE_PART:
-      return source.GetName();
-
-    case SUBSTITUTION_SOURCE_DIR:
-      if (source.is_system_absolute())
-        return DirectoryWithNoLastSlash(source.GetDir());
-      to_rebase = DirectoryWithNoLastSlash(source.GetDir());
-      break;
-
-    case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
-      if (source.is_system_absolute())
-        return DirectoryWithNoLastSlash(source.GetDir());
-      return RebasePath(DirectoryWithNoLastSlash(source.GetDir()),
-                        SourceDir("//"),
+  if (type == &SubstitutionSource) {
+    if (source.is_system_absolute())
+      return source.value();
+    to_rebase = source.value();
+  } else if (type == &SubstitutionSourceNamePart) {
+    return FindFilenameNoExtension(&source.value()).as_string();
+  } else if (type == &SubstitutionSourceFilePart) {
+    return source.GetName();
+  } else if (type == &SubstitutionSourceDir) {
+    if (source.is_system_absolute())
+      return DirectoryWithNoLastSlash(source.GetDir());
+    to_rebase = DirectoryWithNoLastSlash(source.GetDir());
+  } else if (type == &SubstitutionSourceRootRelativeDir) {
+    if (source.is_system_absolute())
+      return DirectoryWithNoLastSlash(source.GetDir());
+    return RebasePath(DirectoryWithNoLastSlash(source.GetDir()),
+                      SourceDir("//"),
+                      settings->build_settings()->root_path_utf8());
+  } else if (type == &SubstitutionSourceGenDir) {
+    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
+  } else if (type == &SubstitutionSourceOutDir) {
+    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
+  } else if (type == &SubstitutionSourceTargetRelative) {
+    if (target) {
+      return RebasePath(source.value(), target->label().dir(),
                         settings->build_settings()->root_path_utf8());
-
-    case SUBSTITUTION_SOURCE_GEN_DIR:
-      to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
-          BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
-      break;
-
-    case SUBSTITUTION_SOURCE_OUT_DIR:
-      to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
-          BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
-      break;
-
-    case SUBSTITUTION_SOURCE_TARGET_RELATIVE:
-      if (target) {
-        return RebasePath(source.value(), target->label().dir(),
-                          settings->build_settings()->root_path_utf8());
-      }
-      NOTREACHED() << "Cannot use substitution " << kSubstitutionNames[type]
-                   << " without target";
-      return std::string();
-
-    default:
-      NOTREACHED() << "Unsupported substitution for this function: "
-                   << kSubstitutionNames[type];
-      return std::string();
+    }
+    NOTREACHED() << "Cannot use substitution " << type->name
+                 << " without target";
+    return std::string();
+  } else {
+    NOTREACHED() << "Unsupported substitution for this function: "
+                 << type->name;
+    return std::string();
   }
 
   // If we get here, the result is a path that should be made relative or
@@ -412,7 +400,7 @@
     const SubstitutionPattern& pattern) {
   std::string result_value;
   for (const auto& subrange : pattern.ranges()) {
-    if (subrange.type == SUBSTITUTION_LITERAL) {
+    if (subrange.type == &SubstitutionLiteral) {
       result_value.append(subrange.literal);
     } else {
       std::string subst;
@@ -435,49 +423,42 @@
 
 // static
 bool SubstitutionWriter::GetTargetSubstitution(const Target* target,
-                                               SubstitutionType type,
+                                               const Substitution* type,
                                                std::string* result) {
-  switch (type) {
-    case SUBSTITUTION_LABEL:
-      // Only include the toolchain for non-default toolchains.
-      *result =
-          target->label().GetUserVisibleName(!target->settings()->is_default());
-      break;
-    case SUBSTITUTION_LABEL_NAME:
-      *result = target->label().name();
-      break;
-    case SUBSTITUTION_ROOT_GEN_DIR:
-      SetDirOrDotWithNoSlash(
-          GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
-              .value(),
-          result);
-      break;
-    case SUBSTITUTION_ROOT_OUT_DIR:
-      SetDirOrDotWithNoSlash(
-          target->settings()->toolchain_output_subdir().value(), result);
-      break;
-    case SUBSTITUTION_TARGET_GEN_DIR:
-      SetDirOrDotWithNoSlash(
-          GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
-          result);
-      break;
-    case SUBSTITUTION_TARGET_OUT_DIR:
-      SetDirOrDotWithNoSlash(
-          GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
-          result);
-      break;
-    case SUBSTITUTION_TARGET_OUTPUT_NAME:
-      *result = target->GetComputedOutputName();
-      break;
-    default:
-      return false;
+  if (type == &SubstitutionLabel) {
+    // Only include the toolchain for non-default toolchains.
+    *result =
+        target->label().GetUserVisibleName(!target->settings()->is_default());
+  } else if (type == &SubstitutionLabelName) {
+    *result = target->label().name();
+  } else if (type == &SubstitutionRootGenDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
+            .value(),
+        result);
+  } else if (type == &SubstitutionRootOutDir) {
+    SetDirOrDotWithNoSlash(
+        target->settings()->toolchain_output_subdir().value(), result);
+  } else if (type == &SubstitutionTargetGenDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
+        result);
+  } else if (type == &SubstitutionTargetOutDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
+        result);
+  } else if (type == &SubstitutionTargetOutputName) {
+    *result = target->GetComputedOutputName();
+  } else {
+    return false;
   }
   return true;
 }
 
 // static
-std::string SubstitutionWriter::GetTargetSubstitution(const Target* target,
-                                                      SubstitutionType type) {
+std::string SubstitutionWriter::GetTargetSubstitution(
+    const Target* target,
+    const Substitution* type) {
   std::string result;
   GetTargetSubstitution(target, type, &result);
   return result;
@@ -490,7 +471,7 @@
     const SubstitutionPattern& pattern) {
   OutputFile result;
   for (const auto& subrange : pattern.ranges()) {
-    if (subrange.type == SUBSTITUTION_LITERAL) {
+    if (subrange.type == &SubstitutionLiteral) {
       result.value().append(subrange.literal);
     } else {
       result.value().append(
@@ -514,7 +495,7 @@
 std::string SubstitutionWriter::GetCompilerSubstitution(
     const Target* target,
     const SourceFile& source,
-    SubstitutionType type) {
+    const Substitution* type) {
   // First try the common tool ones.
   std::string result;
   if (GetTargetSubstitution(target, type, &result))
@@ -533,7 +514,7 @@
     const SubstitutionPattern& pattern) {
   OutputFile result;
   for (const auto& subrange : pattern.ranges()) {
-    if (subrange.type == SUBSTITUTION_LITERAL) {
+    if (subrange.type == &SubstitutionLiteral) {
       result.value().append(subrange.literal);
     } else {
       result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
@@ -553,44 +534,43 @@
 }
 
 // static
-std::string SubstitutionWriter::GetLinkerSubstitution(const Target* target,
-                                                      const Tool* tool,
-                                                      SubstitutionType type) {
+std::string SubstitutionWriter::GetLinkerSubstitution(
+    const Target* target,
+    const Tool* tool,
+    const Substitution* type) {
   // First try the common tool ones.
   std::string result;
   if (GetTargetSubstitution(target, type, &result))
     return result;
 
   // Fall-through to the linker-specific ones.
-  switch (type) {
-    case SUBSTITUTION_OUTPUT_DIR:
-      // Use the target's value if there is one (it will have no expansion
-      // patterns since it can directly use GN variables to compute whatever
-      // path it wants), or the tool's default (which will contain further
-      // expansions).
-      if (target->output_dir().is_null()) {
-        return ApplyPatternToLinkerAsOutputFile(target, tool,
-                                                tool->default_output_dir())
-            .value();
-      }
-      SetDirOrDotWithNoSlash(
-          RebasePath(target->output_dir().value(),
-                     target->settings()->build_settings()->build_dir()),
-          &result);
-      return result;
+  if (type == &CSubstitutionOutputDir) {
+    // Use the target's value if there is one (it will have no expansion
+    // patterns since it can directly use GN variables to compute whatever
+    // path it wants), or the tool's default (which will contain further
+    // expansions).
+    if (target->output_dir().is_null()) {
+      return ApplyPatternToLinkerAsOutputFile(target, tool,
+                                              tool->default_output_dir())
+          .value();
+    }
+    SetDirOrDotWithNoSlash(
+        RebasePath(target->output_dir().value(),
+                   target->settings()->build_settings()->build_dir()),
+        &result);
+    return result;
+  } else if (type == &CSubstitutionOutputExtension) {
+    // Use the extension provided on the target if specified, otherwise
+    // fall back on the default. Note that the target's output extension
+    // does not include the dot but the tool's does.
+    if (!target->output_extension_set())
+      return tool->default_output_extension();
+    if (target->output_extension().empty())
+      return std::string();  // Explicitly set to no extension.
+    return std::string(".") + target->output_extension();
 
-    case SUBSTITUTION_OUTPUT_EXTENSION:
-      // Use the extension provided on the target if specified, otherwise
-      // fall back on the default. Note that the target's output extension
-      // does not include the dot but the tool's does.
-      if (!target->output_extension_set())
-        return tool->default_output_extension();
-      if (target->output_extension().empty())
-        return std::string();  // Explicitly set to no extension.
-      return std::string(".") + target->output_extension();
-
-    default:
-      NOTREACHED();
-      return std::string();
+  } else {
+    NOTREACHED();
+    return std::string();
   }
 }
diff --git a/tools/gn/substitution_writer.h b/tools/gn/substitution_writer.h
index 530f064..24cc283 100644
--- a/tools/gn/substitution_writer.h
+++ b/tools/gn/substitution_writer.h
@@ -155,7 +155,7 @@
       const Target* target,
       const Settings* settings,
       const SourceFile& source,
-      const std::vector<SubstitutionType>& types,
+      const std::vector<const Substitution*>& types,
       const EscapeOptions& escape_options,
       std::ostream& out);
 
@@ -168,7 +168,7 @@
   static std::string GetSourceSubstitution(const Target* target,
                                            const Settings* settings,
                                            const SourceFile& source,
-                                           SubstitutionType type,
+                                           const Substitution* type,
                                            OutputStyle output_style,
                                            const SourceDir& relative_to);
 
@@ -190,10 +190,10 @@
   // compiler and linker ones which will fall through if it's not a common tool
   // one).
   static bool GetTargetSubstitution(const Target* target,
-                                    SubstitutionType type,
+                                    const Substitution* type,
                                     std::string* result);
   static std::string GetTargetSubstitution(const Target* target,
-                                           SubstitutionType type);
+                                           const Substitution* type);
 
   // Compiler substitutions ----------------------------------------------------
   //
@@ -214,7 +214,7 @@
   // directory.
   static std::string GetCompilerSubstitution(const Target* target,
                                              const SourceFile& source,
-                                             SubstitutionType type);
+                                             const Substitution* type);
 
   // Linker substitutions ------------------------------------------------------
 
@@ -232,7 +232,7 @@
   // directory.
   static std::string GetLinkerSubstitution(const Target* target,
                                            const Tool* tool,
-                                           SubstitutionType type);
+                                           const Substitution* type);
 };
 
 #endif  // TOOLS_GN_SUBSTITUTION_WRITER_H_
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index cc476e4..bea8fec 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <sstream>
 
+#include "tools/gn/c_substitution_type.h"
 #include "tools/gn/err.h"
 #include "tools/gn/escape.h"
 #include "tools/gn/substitution_list.h"
@@ -62,10 +63,10 @@
 TEST(SubstitutionWriter, WriteNinjaVariablesForSource) {
   TestWithScope setup;
 
-  std::vector<SubstitutionType> types;
-  types.push_back(SUBSTITUTION_SOURCE);
-  types.push_back(SUBSTITUTION_SOURCE_NAME_PART);
-  types.push_back(SUBSTITUTION_SOURCE_DIR);
+  std::vector<const Substitution*> types;
+  types.push_back(&SubstitutionSource);
+  types.push_back(&SubstitutionSourceNamePart);
+  types.push_back(&SubstitutionSourceDir);
 
   EscapeOptions options;
   options.mode = ESCAPE_NONE;
@@ -123,61 +124,58 @@
 
   // Try all possible templates with a normal looking string.
   EXPECT_EQ("../../foo/bar/baz.txt",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSource));
   EXPECT_EQ("//foo/bar/baz.txt",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSource));
 
   EXPECT_EQ("baz",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_NAME_PART));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceNamePart));
   EXPECT_EQ("baz",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_NAME_PART));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceNamePart));
 
   EXPECT_EQ("baz.txt",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_FILE_PART));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceFilePart));
   EXPECT_EQ("baz.txt",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_FILE_PART));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceFilePart));
 
   EXPECT_EQ("../../foo/bar",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_DIR));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceDir));
   EXPECT_EQ("//foo/bar",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_DIR));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceDir));
 
   EXPECT_EQ("foo/bar", GetRelSubst("//foo/bar/baz.txt",
-                                   SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR));
+                                   &SubstitutionSourceRootRelativeDir));
   EXPECT_EQ("foo/bar", GetAbsSubst("//foo/bar/baz.txt",
-                                   SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR));
+                                   &SubstitutionSourceRootRelativeDir));
 
   EXPECT_EQ("gen/foo/bar",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_GEN_DIR));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceGenDir));
   EXPECT_EQ("//out/Debug/gen/foo/bar",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_GEN_DIR));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceGenDir));
 
   EXPECT_EQ("obj/foo/bar",
-            GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceOutDir));
   EXPECT_EQ("//out/Debug/obj/foo/bar",
-            GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceOutDir));
 
   // Operations on an absolute path.
-  EXPECT_EQ("/baz.txt", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE));
-  EXPECT_EQ("/.", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_DIR));
-  EXPECT_EQ("gen/ABS_PATH",
-            GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_GEN_DIR));
-  EXPECT_EQ("obj/ABS_PATH",
-            GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+  EXPECT_EQ("/baz.txt", GetRelSubst("/baz.txt", &SubstitutionSource));
+  EXPECT_EQ("/.", GetRelSubst("/baz.txt", &SubstitutionSourceDir));
+  EXPECT_EQ("gen/ABS_PATH", GetRelSubst("/baz.txt", &SubstitutionSourceGenDir));
+  EXPECT_EQ("obj/ABS_PATH", GetRelSubst("/baz.txt", &SubstitutionSourceOutDir));
 #if defined(OS_WIN)
   EXPECT_EQ("gen/ABS_PATH/C",
-            GetRelSubst("/C:/baz.txt", SUBSTITUTION_SOURCE_GEN_DIR));
+            GetRelSubst("/C:/baz.txt", &SubstitutionSourceGenDir));
   EXPECT_EQ("obj/ABS_PATH/C",
-            GetRelSubst("/C:/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+            GetRelSubst("/C:/baz.txt", &SubstitutionSourceOutDir));
 #endif
 
-  EXPECT_EQ(".",
-            GetRelSubst("//baz.txt", SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR));
+  EXPECT_EQ(".", GetRelSubst("//baz.txt", &SubstitutionSourceRootRelativeDir));
 
   EXPECT_EQ("baz.txt", GetRelSubst("//foo/bar/baz.txt",
-                                   SUBSTITUTION_SOURCE_TARGET_RELATIVE));
+                                   &SubstitutionSourceTargetRelative));
   EXPECT_EQ("baz.txt", GetAbsSubst("//foo/bar/baz.txt",
-                                   SUBSTITUTION_SOURCE_TARGET_RELATIVE));
+                                   &SubstitutionSourceTargetRelative));
 
 #undef GetAbsSubst
 #undef GetRelSubst
@@ -194,31 +192,31 @@
 
   std::string result;
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_LABEL, &result));
+      &target, &SubstitutionLabel, &result));
   EXPECT_EQ("//foo/bar:baz", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_LABEL_NAME, &result));
+      &target, &SubstitutionLabelName, &result));
   EXPECT_EQ("baz", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_ROOT_GEN_DIR, &result));
+      &target, &SubstitutionRootGenDir, &result));
   EXPECT_EQ("gen", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_ROOT_OUT_DIR, &result));
+      &target, &SubstitutionRootOutDir, &result));
   EXPECT_EQ(".", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_TARGET_GEN_DIR, &result));
+      &target, &SubstitutionTargetGenDir, &result));
   EXPECT_EQ("gen/foo/bar", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_TARGET_OUT_DIR, &result));
+      &target, &SubstitutionTargetOutDir, &result));
   EXPECT_EQ("obj/foo/bar", result);
 
   EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
-      &target, SUBSTITUTION_TARGET_OUTPUT_NAME, &result));
+      &target, &SubstitutionTargetOutputName, &result));
   EXPECT_EQ("libbaz", result);
 }
 
@@ -235,10 +233,10 @@
   // of each of those classes of things to make sure this is hooked up.
   EXPECT_EQ("file", SubstitutionWriter::GetCompilerSubstitution(
                         &target, SourceFile("//foo/bar/file.txt"),
-                        SUBSTITUTION_SOURCE_NAME_PART));
+                        &SubstitutionSourceNamePart));
   EXPECT_EQ("gen/foo/bar", SubstitutionWriter::GetCompilerSubstitution(
                                &target, SourceFile("//foo/bar/file.txt"),
-                               SUBSTITUTION_TARGET_GEN_DIR));
+                               &SubstitutionTargetGenDir));
 }
 
 TEST(SubstitutionWriter, LinkerSubstitutions) {
@@ -255,9 +253,9 @@
   // The compiler substitution is just target + OUTPUT_EXTENSION combined. So
   // test one target one plus the output extension.
   EXPECT_EQ(".so", SubstitutionWriter::GetLinkerSubstitution(
-                       &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+                       &target, tool, &CSubstitutionOutputExtension));
   EXPECT_EQ("gen/foo/bar", SubstitutionWriter::GetLinkerSubstitution(
-                               &target, tool, SUBSTITUTION_TARGET_GEN_DIR));
+                               &target, tool, &SubstitutionTargetGenDir));
 
   // Test that we handle paths that end up in the root build dir properly
   // (no leading "./" or "/").
@@ -272,10 +270,10 @@
   // Output extensions can be overridden.
   target.set_output_extension("extension");
   EXPECT_EQ(".extension", SubstitutionWriter::GetLinkerSubstitution(
-                              &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+                              &target, tool, &CSubstitutionOutputExtension));
   target.set_output_extension("");
   EXPECT_EQ("", SubstitutionWriter::GetLinkerSubstitution(
-                    &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+                    &target, tool, &CSubstitutionOutputExtension));
 
   // Output directory is tested in a separate test below.
 }
@@ -304,8 +302,8 @@
   ASSERT_TRUE(output_name.Parse("{{output_dir}}/{{target_output_name}}.exe",
                                 nullptr, &err));
   EXPECT_EQ("./baz/baz.exe",
-            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(&target, tool.get(),
-                                                                 output_name)
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                &target, tool.get(), output_name)
                 .value());
 
   // Override the output name to the root build dir.
@@ -317,7 +315,7 @@
   // Override the output name to a new subdirectory.
   target.set_output_dir(SourceDir("//out/Debug/foo/bar"));
   EXPECT_EQ("foo/bar/baz.exe",
-            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(&target, tool.get(),
-                                                                 output_name)
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                &target, tool.get(), output_name)
                 .value());
 }
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index 9ad8969..a468426 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -368,7 +368,7 @@
     return false;
   }
 
-  if (pattern.ranges()[0].type == SUBSTITUTION_LITERAL) {
+  if (pattern.ranges()[0].type == &SubstitutionLiteral) {
     // If the first thing is a literal, it must start with the output dir.
     if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(),
                                    pattern.ranges()[0].literal,
diff --git a/tools/gn/tool.cc b/tools/gn/tool.cc
index 51e0bc2..f60e3d4 100644
--- a/tools/gn/tool.cc
+++ b/tools/gn/tool.cc
@@ -53,14 +53,14 @@
   return false;
 }
 
-bool Tool::ValidateSubstitutionList(const std::vector<SubstitutionType>& list,
+bool Tool::ValidateSubstitutionList(const std::vector<const Substitution*>& list,
                                     const Value* origin,
                                     Err* err) const {
   for (const auto& cur_type : list) {
     if (!ValidateSubstitution(cur_type)) {
       *err = Err(*origin, "Pattern not valid here.",
                  "You used the pattern " +
-                     std::string(kSubstitutionNames[cur_type]) +
+                     std::string(cur_type->name) +
                      " which is not valid\nfor this variable.");
       return false;
     }
diff --git a/tools/gn/tool.h b/tools/gn/tool.h
index 4060527..d800886 100644
--- a/tools/gn/tool.h
+++ b/tools/gn/tool.h
@@ -53,7 +53,7 @@
   virtual void SetComplete() = 0;
 
   // Validate substitutions in this tool.
-  virtual bool ValidateSubstitution(SubstitutionType sub_type) const = 0;
+  virtual bool ValidateSubstitution(const Substitution* sub_type) const = 0;
 
   // Manual RTTI
   virtual CTool* AsC();
@@ -183,7 +183,7 @@
   // SetComplete().
   bool IsPatternInOutputList(const SubstitutionList& output_list,
                              const SubstitutionPattern& pattern) const;
-  bool ValidateSubstitutionList(const std::vector<SubstitutionType>& list,
+  bool ValidateSubstitutionList(const std::vector<const Substitution*>& list,
                                 const Value* origin,
                                 Err* err) const;
   bool ValidateOutputs(Err* err) const;