Merge pull request #131 from EricRahm/use_enums_in_ir

Split out IR enums
diff --git a/.bazelrc b/.bazelrc
index 85399b9..f3470b4 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,3 +1,5 @@
+# Emboss doesn't (yet) support bzlmod.
+common --noenable_bzlmod
 # Emboss (at least notionally) supports C++11 until (at least) 2027.  However,
 # Googletest requires C++14, which means that all of the Emboss unit tests
 # require C++14 to run.
diff --git a/WORKSPACE b/WORKSPACE
index 691fa88..90179b8 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,25 +1,36 @@
 workspace(name = "com_google_emboss")
 
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 
 # googletest
 git_repository(
     name = "com_google_googletest",
-    remote = "https://github.com/google/googletest",
     commit = "2f2e72bae991138cedd0e3d06a115022736cd568",
+    remote = "https://github.com/google/googletest",
     shallow_since = "1563302555 -0400",
 )
 
 git_repository(
     name = "com_google_absl",
-    remote = "https://github.com/abseil/abseil-cpp",
     commit = "3020b58f0d987073b8adab204426f82c3f60b283",
+    remote = "https://github.com/abseil/abseil-cpp",
     shallow_since = "1562769772 +0000",
 )
 
 http_archive(
-  name = "bazel_skylib",
-  urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz"],
-  sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728",
+    name = "bazel_skylib",
+    sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728",
+    urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz"],
 )
+
+http_archive(
+    name = "rules_python",
+    sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311",
+    strip_prefix = "rules_python-0.31.0",
+    url = "https://github.com/bazelbuild/rules_python/releases/download/0.31.0/rules_python-0.31.0.tar.gz",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories")
+
+py_repositories()
diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD
index 427b724..e9ae268 100644
--- a/compiler/back_end/cpp/BUILD
+++ b/compiler/back_end/cpp/BUILD
@@ -14,6 +14,9 @@
 
 # Emboss C++ code generator.
 
+load("@rules_python//python:py_binary.bzl", "py_binary")
+load("@rules_python//python:py_library.bzl", "py_library")
+load("@rules_python//python:py_test.bzl", "py_test")
 load(":build_defs.bzl", "emboss_cc_test")
 
 package(
@@ -37,7 +40,7 @@
 py_library(
     name = "attributes",
     srcs = ["attributes.py"],
-    deps = []
+    deps = [],
 )
 
 py_library(
diff --git a/compiler/back_end/cpp/emboss_codegen_cpp.py b/compiler/back_end/cpp/emboss_codegen_cpp.py
index 77bcb84..ed5e66e 100644
--- a/compiler/back_end/cpp/emboss_codegen_cpp.py
+++ b/compiler/back_end/cpp/emboss_codegen_cpp.py
@@ -48,16 +48,30 @@
   return parser.parse_args(argv[1:])
 
 
-def _show_errors(errors, ir, flags):
+def _show_errors(errors, ir, color_output):
   """Prints errors with source code snippets."""
   source_codes = {}
   for module in ir.module:
     source_codes[module.source_file_name] = module.source_text
-  use_color = (flags.color_output == "always" or
-               (flags.color_output in ("auto", "if_tty") and
+  use_color = (color_output == "always" or
+               (color_output in ("auto", "if_tty") and
                 os.isatty(sys.stderr.fileno())))
   print(error.format_errors(errors, source_codes, use_color), file=sys.stderr)
 
+def generate_headers_and_log_errors(ir, color_output):
+  """Generates a C++ header and logs any errors.
+
+  Arguments:
+    ir: EmbossIr of the module.
+    color_output: "always", "never", "if_tty", "auto"
+
+  Returns:
+    A tuple of (header, errors)
+  """
+  header, errors = header_generator.generate_header(ir)
+  if errors:
+    _show_errors(errors, ir, color_output)
+  return (header, errors)
 
 def main(flags):
   if flags.input_file:
@@ -65,9 +79,8 @@
       ir = ir_pb2.EmbossIr.from_json(f.read())
   else:
     ir = ir_pb2.EmbossIr.from_json(sys.stdin.read())
-  header, errors = header_generator.generate_header(ir)
+  header, errors = generate_headers_and_log_errors(ir, flags.color_output)
   if errors:
-    _show_errors(errors, ir, flags)
     return 1
   if flags.output_file:
     with open(flags.output_file, "w") as f:
diff --git a/compiler/back_end/util/BUILD b/compiler/back_end/util/BUILD
index 4598d2f..97c6e07 100644
--- a/compiler/back_end/util/BUILD
+++ b/compiler/back_end/util/BUILD
@@ -14,6 +14,9 @@
 
 # Shared utilities for Emboss back ends.
 
+load("@rules_python//python:py_library.bzl", "py_library")
+load("@rules_python//python:py_test.bzl", "py_test")
+
 package(
     default_visibility = ["//compiler:__subpackages__"],
 )
diff --git a/compiler/front_end/BUILD b/compiler/front_end/BUILD
index 776efed..136095f 100644
--- a/compiler/front_end/BUILD
+++ b/compiler/front_end/BUILD
@@ -18,6 +18,10 @@
 # intermediate representation (IR).  The IR is passed to back end code
 # generators to generate code in various languages.
 
+load("@rules_python//python:py_binary.bzl", "py_binary")
+load("@rules_python//python:py_library.bzl", "py_library")
+load("@rules_python//python:py_test.bzl", "py_test")
+
 package(
     default_visibility = [
         "//:__subpackages__",
@@ -135,8 +139,8 @@
         ":tokenizer",
         ":type_check",
         ":write_inference",
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:parser_types",
         "//compiler/util:resources",
     ],
@@ -151,8 +155,8 @@
     python_version = "PY3",
     deps = [
         ":glue",
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:parser_types",
         "//compiler/util:test_util",
     ],
@@ -163,8 +167,8 @@
     srcs = ["synthetics.py"],
     visibility = ["//visibility:private"],
     deps = [
-        "//compiler/util:ir_pb2",
         "//compiler/util:expression_parser",
+        "//compiler/util:ir_pb2",
         "//compiler/util:traverse_ir",
     ],
 )
@@ -185,8 +189,8 @@
     srcs = ["symbol_resolver.py"],
     visibility = ["//visibility:private"],
     deps = [
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:ir_util",
         "//compiler/util:traverse_ir",
     ],
@@ -257,8 +261,8 @@
     deps = [
         ":attribute_checker",
         ":glue",
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:ir_util",
         "//compiler/util:test_util",
     ],
@@ -269,8 +273,8 @@
     srcs = ["type_check.py"],
     deps = [
         ":attributes",
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:ir_util",
         "//compiler/util:traverse_ir",
     ],
@@ -321,8 +325,8 @@
     ],
     deps = [
         ":attributes",
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:ir_util",
         "//compiler/util:resources",
         "//compiler/util:traverse_ir",
@@ -345,8 +349,8 @@
     name = "dependency_checker",
     srcs = ["dependency_checker.py"],
     deps = [
-        "//compiler/util:ir_pb2",
         "//compiler/util:error",
+        "//compiler/util:ir_pb2",
         "//compiler/util:ir_util",
         "//compiler/util:traverse_ir",
     ],
diff --git a/compiler/front_end/constraints.py b/compiler/front_end/constraints.py
index 55285f8..107b55e 100644
--- a/compiler/front_end/constraints.py
+++ b/compiler/front_end/constraints.py
@@ -537,6 +537,8 @@
   # errors are just returned, rather than appended to a shared list.
   errors += _integer_bounds_errors_for_expression(expression, source_file_name)
 
+def _attribute_in_attribute_action(a):
+  return {"in_attribute": a}
 
 def check_constraints(ir):
   """Checks miscellaneous validity constraints in ir.
@@ -597,7 +599,7 @@
       parameters={"errors": errors})
   traverse_ir.fast_traverse_ir_top_down(
       ir, [ir_pb2.Expression], _check_bounds_on_runtime_integer_expressions,
-      incidental_actions={ir_pb2.Attribute: lambda a: {"in_attribute": a}},
+      incidental_actions={ir_pb2.Attribute: _attribute_in_attribute_action},
       skip_descendants_of={ir_pb2.EnumValue, ir_pb2.Expression},
       parameters={"errors": errors, "in_attribute": None})
   traverse_ir.fast_traverse_ir_top_down(
diff --git a/compiler/front_end/emboss_front_end.py b/compiler/front_end/emboss_front_end.py
index 7df6782..6232a40 100644
--- a/compiler/front_end/emboss_front_end.py
+++ b/compiler/front_end/emboss_front_end.py
@@ -86,14 +86,14 @@
   return parser.parse_args(argv[1:])
 
 
-def _show_errors(errors, ir, flags):
+def _show_errors(errors, ir, color_output):
   """Prints errors with source code snippets."""
   source_codes = {}
   if ir:
     for module in ir.module:
       source_codes[module.source_file_name] = module.source_text
-  use_color = (flags.color_output == "always" or
-               (flags.color_output in ("auto", "if_tty") and
+  use_color = (color_output == "always" or
+               (color_output in ("auto", "if_tty") and
                 os.isatty(sys.stderr.fileno())))
   print(error.format_errors(errors, source_codes, use_color), file=sys.stderr)
 
@@ -128,12 +128,28 @@
 
   return _find_and_read
 
+def parse_and_log_errors(input_file, import_dirs, color_output):
+  """Fully parses an .emb and logs any errors.
+
+  Arguments:
+    input_file: The path of the module source file.
+    import_dirs: Directories to search for imported dependencies.
+    color_output: Used when logging errors: "always", "never", "if_tty", "auto"
+
+  Returns:
+    (ir, debug_info, errors)
+  """
+  ir, debug_info, errors = glue.parse_emboss_file(
+      input_file, _find_in_dirs_and_read(import_dirs))
+  if errors:
+    _show_errors(errors, ir, color_output)
+
+  return (ir, debug_info, errors)
 
 def main(flags):
-  ir, debug_info, errors = glue.parse_emboss_file(
-      flags.input_file[0], _find_in_dirs_and_read(flags.import_dirs))
+  ir, debug_info, errors = parse_and_log_errors(
+    flags.input_file[0], flags.import_dirs, flags.color_output)
   if errors:
-    _show_errors(errors, ir, flags)
     return 1
   main_module_debug_info = debug_info.modules[flags.input_file[0]]
   if flags.debug_show_tokenization:
diff --git a/compiler/front_end/symbol_resolver.py b/compiler/front_end/symbol_resolver.py
index 54bcdcc..f4fb581 100644
--- a/compiler/front_end/symbol_resolver.py
+++ b/compiler/front_end/symbol_resolver.py
@@ -468,6 +468,8 @@
       "visible_scopes": (field.name.canonical_name,) + visible_scopes,
   }
 
+def _module_source_from_table_action(m, table):
+  return {"module": table[m.source_file_name]}
 
 def _resolve_symbols_from_table(ir, table):
   """Resolves all references in the given IR, given the constructed table."""
@@ -477,7 +479,7 @@
   traverse_ir.fast_traverse_ir_top_down(
       ir, [ir_pb2.Import], _add_import_to_scope,
       incidental_actions={
-          ir_pb2.Module: lambda m, table: {"module": table[m.source_file_name]},
+          ir_pb2.Module: _module_source_from_table_action,
       },
       parameters={"errors": errors, "table": table})
   if errors:
@@ -490,7 +492,6 @@
       incidental_actions={
           ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition,
           ir_pb2.Module: _set_visible_scopes_for_module,
-          ir_pb2.Field: lambda f: {"field": f},
           ir_pb2.Attribute: _set_visible_scopes_for_attribute,
       },
       parameters={"table": table, "errors": errors, "field": None})
@@ -500,7 +501,6 @@
       incidental_actions={
           ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition,
           ir_pb2.Module: _set_visible_scopes_for_module,
-          ir_pb2.Field: lambda f: {"field": f},
           ir_pb2.Attribute: _set_visible_scopes_for_attribute,
       },
       parameters={"table": table, "errors": errors, "field": None})
@@ -515,7 +515,6 @@
       incidental_actions={
           ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition,
           ir_pb2.Module: _set_visible_scopes_for_module,
-          ir_pb2.Field: lambda f: {"field": f},
           ir_pb2.Attribute: _set_visible_scopes_for_attribute,
       },
       parameters={"errors": errors, "field": None})
diff --git a/compiler/util/BUILD b/compiler/util/BUILD
index d0e3d3f..ee59dfe 100644
--- a/compiler/util/BUILD
+++ b/compiler/util/BUILD
@@ -14,6 +14,9 @@
 
 # Shared utilities for Emboss back ends.
 
+load("@rules_python//python:py_library.bzl", "py_library")
+load("@rules_python//python:py_test.bzl", "py_test")
+
 package(
     default_visibility = ["//compiler:__subpackages__"],
 )
@@ -100,8 +103,8 @@
     name = "traverse_ir",
     srcs = ["traverse_ir.py"],
     deps = [
-        ":simple_memoizer",
         ":ir_pb2",
+        ":simple_memoizer",
     ],
 )
 
@@ -110,8 +113,8 @@
     srcs = ["traverse_ir_test.py"],
     python_version = "PY3",
     deps = [
-        ":traverse_ir",
         ":ir_pb2",
+        ":traverse_ir",
     ],
 )
 
@@ -128,8 +131,8 @@
     srcs = ["parser_types_test.py"],
     python_version = "PY3",
     deps = [
-        ":parser_types",
         ":ir_pb2",
+        ":parser_types",
     ],
 )
 
diff --git a/compiler/util/traverse_ir.py b/compiler/util/traverse_ir.py
index fc04ba8..3bd95c3 100644
--- a/compiler/util/traverse_ir.py
+++ b/compiler/util/traverse_ir.py
@@ -17,27 +17,98 @@
 import inspect
 
 from compiler.util import ir_pb2
+from compiler.util import simple_memoizer
+
+
+class _FunctionCaller:
+  """Provides a template for setting up a generic call to a function.
+
+  The function parameters are inspected at run-time to build up a set of valid
+  and required arguments. When invoking the function unneccessary parameters
+  will be trimmed out. If arguments are missing an assertion will be triggered.
+
+  This is currently limited to functions that have at least one positional
+  parameter.
+
+  Example usage:
+  ```
+  def func_1(a, b, c=2): pass
+  def func_2(a, d): pass
+  caller_1 = _FunctionCaller(func_1)
+  caller_2 = _FunctionCaller(func_2)
+  generic_params = {"b": 2, "c": 3, "d": 4}
+
+  # Equivalent of: func_1(a, b=2, c=3)
+  caller_1.invoke(a, generic_params)
+
+  # Equivalent of: func_2(a, d=4)
+  caller_2.invoke(a, generic_params)
+  """
+
+  def __init__(self, function):
+    self.function = function
+    self.needs_filtering = True
+    self.valid_arg_names = set()
+    self.required_arg_names = set()
+
+    argspec = inspect.getfullargspec(function)
+    if argspec.varkw:
+      # If the function accepts a kwargs parameter, then it will accept all
+      # arguments.
+      # Note: this isn't technically true if one of the keyword arguments has the
+      # same name as one of the positional arguments.
+      self.needs_filtering = False
+    else:
+      # argspec.args is a list of all parameter names excluding keyword only
+      # args. The first element is our required positional_arg and should be
+      # ignored.
+      args = argspec.args[1:]
+      self.valid_arg_names.update(args)
+
+      # args.kwonlyargs gives us the list of keyword only args which are
+      # also valid.
+      self.valid_arg_names.update(argspec.kwonlyargs)
+
+      # Required args are positional arguments that don't have defaults.
+      # Keyword only args are always optional and can be ignored. Args with
+      # defaults are the last elements of the argsepec.args list and should
+      # be ignored.
+      if argspec.defaults:
+        # Trim the arguments with defaults.
+        args = args[: -len(argspec.defaults)]
+      self.required_arg_names.update(args)
+
+  def invoke(self, positional_arg, keyword_args):
+    """Invokes the function with the given args."""
+    if self.needs_filtering:
+      # Trim to just recognized args.
+      matched_args = {
+          k: v for k, v in keyword_args.items() if k in self.valid_arg_names
+      }
+      # Check if any required args are missing.
+      missing_args = self.required_arg_names.difference(matched_args.keys())
+      assert not missing_args, (
+          f"Attempting to call '{self.function.__name__}'; "
+          f"missing {missing_args} (have {set(keyword_args.keys())})"
+      )
+      keyword_args = matched_args
+
+    return self.function(positional_arg, **keyword_args)
+
+
+@simple_memoizer.memoize
+def _memoized_caller(function):
+  default_lambda_name = (lambda: None).__name__
+  assert (
+      callable(function) and not function.__name__ == default_lambda_name
+  ), "For performance reasons actions must be defined as static functions"
+  return _FunctionCaller(function)
 
 
 def _call_with_optional_args(function, positional_arg, keyword_args):
   """Calls function with whatever keyword_args it will accept."""
-  argspec = inspect.getfullargspec(function)
-  if argspec.varkw:
-    # If the function accepts a kwargs parameter, then it will accept all
-    # arguments.
-    # Note: this isn't technically true if one of the keyword arguments has the
-    # same name as one of the positional arguments.
-    return function(positional_arg, **keyword_args)
-  else:
-    ok_arguments = {}
-    for name in keyword_args:
-      if name in argspec.args[1:] or name in argspec.kwonlyargs:
-        ok_arguments[name] = keyword_args[name]
-    for name in argspec.args[1:len(argspec.args) - len(argspec.defaults or [])]:
-      assert name in ok_arguments, (
-          "Attempting to call '{}'; missing '{}' (have '{!r}')".format(
-              function.__name__, name, list(keyword_args.keys())))
-    return function(positional_arg, **ok_arguments)
+  caller = _memoized_caller(function)
+  return caller.invoke(positional_arg, keyword_args)
 
 
 def _fast_traverse_proto_top_down(proto, incidental_actions, pattern,
@@ -181,6 +252,17 @@
 
 _FIELDS_TO_SCAN_BY_CURRENT_AND_TARGET = _fields_to_scan_by_current_and_target()
 
+def _emboss_ir_action(ir):
+  return {"ir": ir}
+
+def _module_action(m):
+  return {"source_file_name": m.source_file_name}
+
+def _type_definition_action(t):
+  return {"type_definition": t}
+
+def _field_action(f):
+  return {"field": f}
 
 def fast_traverse_ir_top_down(ir, pattern, action, incidental_actions=None,
                               skip_descendants_of=(), parameters=None):
@@ -269,10 +351,10 @@
     None
   """
   all_incidental_actions = {
-      ir_pb2.EmbossIr: [lambda ir: {"ir": ir}],
-      ir_pb2.Module: [lambda m: {"source_file_name": m.source_file_name}],
-      ir_pb2.TypeDefinition: [lambda t: {"type_definition": t}],
-      ir_pb2.Field: [lambda f: {"field": f}],
+      ir_pb2.EmbossIr: [_emboss_ir_action],
+      ir_pb2.Module: [_module_action],
+      ir_pb2.TypeDefinition: [_type_definition_action],
+      ir_pb2.Field: [_field_action],
   }
   if incidental_actions:
     for key, incidental_action in incidental_actions.items():
diff --git a/doc/BUILD b/doc/BUILD
index cf661bc..b9dbd09 100644
--- a/doc/BUILD
+++ b/doc/BUILD
@@ -20,8 +20,8 @@
 filegroup(
     name = "grammar_md",
     srcs = [
-        "grammar.md",
         "__init__.py",
+        "grammar.md",
     ],
     # This should only be needed by docs_are_up_to_date_test.
     visibility = ["//:__subpackages__"],
diff --git a/embossc b/embossc
index cee92d8..0dac79f 100755
--- a/embossc
+++ b/embossc
@@ -18,10 +18,8 @@
 
 import argparse
 import os
-import subprocess
 import sys
 
-
 def _parse_args(argv):
   parser = argparse.ArgumentParser(description="Emboss compiler")
   parser.add_argument("--color-output",
@@ -59,48 +57,28 @@
 
 
 def main(argv):
-
   flags = _parse_args(argv)
   base_path = os.path.dirname(__file__) or "."
-  subprocess_environment = os.environ.copy()
+  sys.path.append(base_path)
 
-  if subprocess_environment.get("PYTHONPATH"):
-    subprocess_environment["PYTHONPATH"] = (
-      base_path + ":" + subprocess_environment.get("PYTHONPATH"))
-  else:
-    subprocess_environment["PYTHONPATH"] = base_path
-
-  front_end_args = [
-      sys.executable,
-      os.path.join(base_path, "compiler", "front_end", "emboss_front_end.py"),
-      "--output-ir-to-stdout",
-      "--color-output", flags.color_output,
-  ]
-
-  for import_dir in flags.import_dirs:
-    front_end_args.extend(["--import-dir", import_dir])
-
-  front_end_args.append(flags.input_file[0])
-  front_end_status = subprocess.run(front_end_args,
-                                    stdout=subprocess.PIPE,
-                                    env=subprocess_environment)
-
-  if front_end_status.returncode != 0:
-    return front_end_status.returncode
-
-  back_end_status = subprocess.run(
-    [
-      sys.executable,
-      os.path.join(base_path, "compiler", "back_end", "cpp",
-        "emboss_codegen_cpp.py"),
-    ],
-    input=front_end_status.stdout,
-    stdout=subprocess.PIPE,
-    env=subprocess_environment
+  from compiler.back_end.cpp import ( # pylint:disable=import-outside-toplevel
+    emboss_codegen_cpp
+  )
+  from compiler.front_end import ( # pylint:disable=import-outside-toplevel
+    emboss_front_end
   )
 
-  if back_end_status.returncode != 0:
-    return back_end_status.returncode
+  ir, _, errors =  emboss_front_end.parse_and_log_errors(
+      flags.input_file[0], flags.import_dirs, flags.color_output)
+
+  if errors:
+    return 1
+
+  header, errors = emboss_codegen_cpp.generate_headers_and_log_errors(
+      ir, flags.color_output)
+
+  if errors:
+    return 1
 
   if flags.output_file:
     output_file = flags.output_file[0]
@@ -110,8 +88,8 @@
   output_filepath = os.path.join(flags.output_path[0], output_file)
   os.makedirs(os.path.dirname(output_filepath), exist_ok=True)
 
-  with open(output_filepath, "wb") as output:
-    output.write(back_end_status.stdout)
+  with open(output_filepath, "w") as output:
+    output.write(header)
   return 0
 
 
diff --git a/integration/googletest/BUILD b/integration/googletest/BUILD
index b52b103..9c96f08 100644
--- a/integration/googletest/BUILD
+++ b/integration/googletest/BUILD
@@ -19,8 +19,8 @@
     visibility = ["//visibility:public"],
     deps = [
         "//runtime/cpp:cpp_utils",
-        "@com_google_googletest//:gtest",
         "@com_google_absl//absl/memory",
+        "@com_google_googletest//:gtest",
     ],
 )
 
@@ -32,7 +32,7 @@
     copts = ["-Wsign-compare"],
     deps = [
         ":emboss_test_util",
-        "@com_google_googletest//:gtest_main",
         "//testdata:complex_structure_emboss",
+        "@com_google_googletest//:gtest_main",
     ],
 )
diff --git a/runtime/cpp/test/BUILD b/runtime/cpp/test/BUILD
index 49928ea..6aecbf3 100644
--- a/runtime/cpp/test/BUILD
+++ b/runtime/cpp/test/BUILD
@@ -51,8 +51,8 @@
     copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
     deps = [
         "//runtime/cpp:cpp_utils",
-        "@com_google_googletest//:gtest_main",
         "@com_google_absl//absl/strings:str_format",
+        "@com_google_googletest//:gtest_main",
     ],
 )
 
diff --git a/runtime/cpp/test/emboss_memory_util_test.cc b/runtime/cpp/test/emboss_memory_util_test.cc
index 3cfca8b..c4f9ed0 100644
--- a/runtime/cpp/test/emboss_memory_util_test.cc
+++ b/runtime/cpp/test/emboss_memory_util_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <string>
 #if __cplusplus >= 201703L
 #include <string_view>
 #endif  // __cplusplus >= 201703L
@@ -174,13 +175,13 @@
 // std::basic_string<> with non-default trailing template parameters.
 template <class T>
 struct NonstandardAllocator {
-  using value_type = typename ::std::allocator<T>::value_type;
-  using pointer = typename ::std::allocator<T>::pointer;
-  using const_pointer = typename ::std::allocator<T>::const_pointer;
-  using reference = typename ::std::allocator<T>::reference;
-  using const_reference = typename ::std::allocator<T>::const_reference;
-  using size_type = typename ::std::allocator<T>::size_type;
-  using difference_type = typename ::std::allocator<T>::difference_type;
+  using value_type = typename ::std::allocator_traits<::std::allocator<T>>::value_type;
+  using pointer = typename ::std::allocator_traits<::std::allocator<T>>::pointer;
+  using const_pointer = typename ::std::allocator_traits<::std::allocator<T>>::const_pointer;
+  using reference = typename ::std::allocator<T>::value_type &;
+  using const_reference = const typename ::std::allocator_traits<::std::allocator<T>>::value_type &;
+  using size_type = typename ::std::allocator_traits<::std::allocator<T>>::size_type;
+  using difference_type = typename ::std::allocator_traits<::std::allocator<T>>::difference_type;
 
   template <class U>
   struct rebind {
@@ -221,7 +222,7 @@
 typedef ::testing::Types<
     /**/ ::std::vector<char>, ::std::array<char, 8>,
     ::std::vector<unsigned char>, ::std::vector<signed char>, ::std::string,
-    ::std::basic_string<signed char>, ::std::basic_string<unsigned char>,
+    ::std::basic_string<char>,
     ::std::vector<unsigned char, NonstandardAllocator<unsigned char>>,
     ::std::basic_string<char, ::std::char_traits<char>,
                         NonstandardAllocator<char>>>
diff --git a/testdata/BUILD b/testdata/BUILD
index 2f4682b..a04b4a6 100644
--- a/testdata/BUILD
+++ b/testdata/BUILD
@@ -14,10 +14,11 @@
 
 # Shared test data for Emboss.
 
-load("//:build_defs.bzl",
-     "emboss_cc_library",
-     "emboss_library",
-     "cc_emboss_library"
+load(
+    "//:build_defs.bzl",
+    "cc_emboss_library",
+    "emboss_cc_library",
+    "emboss_library",
 )
 
 package(
@@ -27,17 +28,18 @@
 filegroup(
     name = "golden_files",
     srcs = [
+        "golden/__init__.py",
         "golden/span_se_log_file_status.emb",
         "golden/span_se_log_file_status.ir.txt",
         "golden/span_se_log_file_status.parse_tree.txt",
         "golden/span_se_log_file_status.tokens.txt",
-        "golden/__init__.py",
     ],
 )
 
 filegroup(
     name = "test_embs",
     srcs = [
+        "__init__.py",
         "absolute_cpp_namespace.emb",
         "anonymous_bits.emb",
         "bcd.emb",
@@ -50,22 +52,21 @@
         "enum_case.emb",
         "explicit_sizes.emb",
         "float.emb",
+        "import_dir/project/imported.emb",
+        "import_dir/project/importer.emb",
         "imported.emb",
         "imported_genfiles.emb",
         "importer.emb",
-        "import_dir/project/imported.emb",
-        "import_dir/project/importer.emb",
         "int_sizes.emb",
         "nested_structure.emb",
-        "no_cpp_namespace.emb",
         "next_keyword.emb",
+        "no_cpp_namespace.emb",
         "parameters.emb",
         "requires.emb",
         "subtypes.emb",
         "text_format.emb",
         "uint_sizes.emb",
         "virtual_field.emb",
-        "__init__.py",
     ],
 )
 
@@ -180,15 +181,14 @@
     srcs = [
         "import_dir/project/importer.emb",
     ],
+    import_dirs = [
+        "import_dir",
+    ],
     deps = [
         ":import_dir_imported_emboss",
     ],
-    import_dirs = [
-        "import_dir",
-    ]
 )
 
-
 emboss_cc_library(
     name = "importer2_emboss",
     srcs = [