# Copyright 2013 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.

"""Generates C++ source files from a mojom.Module."""

import mojom.generate.generator as generator
import mojom.generate.module as mojom
import mojom.generate.pack as pack
from mojom.generate.template_expander import UseJinja


_kind_to_cpp_type = {
  mojom.BOOL:                  "bool",
  mojom.INT8:                  "int8_t",
  mojom.UINT8:                 "uint8_t",
  mojom.INT16:                 "int16_t",
  mojom.UINT16:                "uint16_t",
  mojom.INT32:                 "int32_t",
  mojom.UINT32:                "uint32_t",
  mojom.FLOAT:                 "float",
  mojom.INT64:                 "int64_t",
  mojom.UINT64:                "uint64_t",
  mojom.DOUBLE:                "double",
}

_kind_to_cpp_literal_suffix = {
  mojom.UINT8:        "U",
  mojom.UINT16:       "U",
  mojom.UINT32:       "U",
  mojom.FLOAT:        "f",
  mojom.UINT64:       "ULL",
}


class _NameFormatter(object):
  """A formatter for the names of kinds or values."""

  def __init__(self, token, variant):
    self._token = token
    self._variant = variant

  def Format(self, separator, prefixed=False, internal=False,
             include_variant=False, omit_namespace_for_module=None,
             flatten_nested_kind=False):
    """Formats the name according to the given configuration.

    Args:
      separator: Separator between different parts of the name.
      prefixed: Whether a leading separator should be added.
      internal: Returns the name in the "internal" namespace.
      include_variant: Whether to include variant as namespace. If |internal| is
          True, then this flag is ignored and variant is not included.
      omit_namespace_for_module: If the token is from the specified module,
          don't add the namespaces of the module to the name.
      flatten_nested_kind: It is allowed to define enums inside structs and
          interfaces. If this flag is set to True, this method concatenates the
          parent kind and the nested kind with '_', instead of treating the
          parent kind as a scope."""

    parts = []
    if self._ShouldIncludeNamespace(omit_namespace_for_module):
      if prefixed:
        parts.append("")
      parts.extend(self._GetNamespace())
      if include_variant and self._variant and not internal:
        parts.append(self._variant)
    parts.extend(self._GetName(internal, flatten_nested_kind))
    return separator.join(parts)

  def FormatForCpp(self, omit_namespace_for_module=None, internal=False,
                   flatten_nested_kind=False):
    return self.Format(
        "::", prefixed=True,
        omit_namespace_for_module=omit_namespace_for_module,
        internal=internal, include_variant=True,
        flatten_nested_kind=flatten_nested_kind)

  def FormatForMojom(self):
    return self.Format(".")

  def _MapKindName(self, token, internal):
    if not internal:
      return token.name
    if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) or
        mojom.IsEnumKind(token)):
      return token.name + "_Data"
    return token.name

  def _GetName(self, internal, flatten_nested_kind):
    if isinstance(self._token, mojom.EnumValue):
      name_parts = _NameFormatter(self._token.enum, self._variant)._GetName(
          internal, flatten_nested_kind)
      name_parts.append(self._token.name)
      return name_parts

    name_parts = []
    if internal:
      name_parts.append("internal")

    if (flatten_nested_kind and mojom.IsEnumKind(self._token) and
        self._token.parent_kind):
      name = "%s_%s" % (self._token.parent_kind.name,
                        self._MapKindName(self._token, internal))
      name_parts.append(name)
      return name_parts

    if self._token.parent_kind:
      name_parts.append(self._MapKindName(self._token.parent_kind, internal))
    name_parts.append(self._MapKindName(self._token, internal))
    return name_parts

  def _ShouldIncludeNamespace(self, omit_namespace_for_module):
    return self._token.module and (
        not omit_namespace_for_module or
        self._token.module.path != omit_namespace_for_module.path)

  def _GetNamespace(self):
    if self._token.module:
      return NamespaceToArray(self._token.module.namespace)


def NamespaceToArray(namespace):
  return namespace.split(".") if namespace else []


def GetWtfHashFnNameForEnum(enum):
  return _NameFormatter(enum, None).Format("_", internal=True,
                                           flatten_nested_kind=True) + "HashFn"


def IsNativeOnlyKind(kind):
  return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \
      kind.native_only


def UseCustomSerializer(kind):
  return mojom.IsStructKind(kind) and kind.custom_serializer


def AllEnumValues(enum):
  """Return all enum values associated with an enum.

  Args:
    enum: {mojom.Enum} The enum type.

  Returns:
   {Set[int]} The values.
  """
  return set(field.numeric_value for field in enum.fields)


def GetCppPodType(kind):
  return _kind_to_cpp_type[kind]


def RequiresContextForDataView(kind):
  for field in kind.fields:
    if mojom.IsReferenceKind(field.kind):
      return True
  return False


def ShouldInlineStruct(struct):
  # TODO(darin): Base this on the size of the wrapper class.
  if len(struct.fields) > 4:
    return False
  for field in struct.fields:
    if mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind):
      return False
  return True


def ShouldInlineUnion(union):
  return not any(
      mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind)
           for field in union.fields)


class StructConstructor(object):
  """Represents a constructor for a generated struct.

  Fields:
    fields: {[Field]} All struct fields in order.
    params: {[Field]} The fields that are passed as params.
  """

  def __init__(self, fields, params):
    self._fields = fields
    self._params = set(params)

  @property
  def params(self):
    return [field for field in self._fields if field in self._params]

  @property
  def fields(self):
    for field in self._fields:
      yield (field, field in self._params)


class Generator(generator.Generator):
  def __init__(self, *args, **kwargs):
    super(Generator, self).__init__(*args, **kwargs)

  def _GetExtraTraitsHeaders(self):
    extra_headers = set()
    for typemap in self._GetAllUsedTypemaps():
      extra_headers.update(typemap.get("traits_headers", []))
    return sorted(extra_headers)

  def _GetAllUsedTypemaps(self):
    """Returns the typemaps for types needed for serialization in this module.

    A type is needed for serialization if it is contained by a struct or union
    defined in this module, is a parameter of a message in an interface in
    this module or is contained within another type needed for serialization.
    """
    used_typemaps = []
    seen_types = set()
    def AddKind(kind):
      if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) or
          mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) or
          mojom.IsAnyHandleKind(kind) or
          mojom.IsInterfaceKind(kind) or
          mojom.IsInterfaceRequestKind(kind) or
          mojom.IsAssociatedKind(kind)):
        pass
      elif mojom.IsArrayKind(kind):
        AddKind(kind.kind)
      elif mojom.IsMapKind(kind):
        AddKind(kind.key_kind)
        AddKind(kind.value_kind)
      else:
        name = self._GetFullMojomNameForKind(kind)
        if name in seen_types:
          return
        seen_types.add(name)

        typemap = self.typemap.get(name, None)
        if typemap:
          used_typemaps.append(typemap)
        if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
          for field in kind.fields:
            AddKind(field.kind)

    for kind in self.module.structs + self.module.unions:
      for field in kind.fields:
        AddKind(field.kind)

    for interface in self.module.interfaces:
      for method in interface.methods:
        for parameter in method.parameters + (method.response_parameters or []):
          AddKind(parameter.kind)

    return used_typemaps

  def _GetExtraPublicHeaders(self):
    headers = set()

    all_enums = list(self.module.enums)
    for struct in self.module.structs:
      all_enums.extend(struct.enums)
    for interface in self.module.interfaces:
      all_enums.extend(interface.enums)
      if interface.uuid:
        headers.add('base/token.h')

    types = set(self._GetFullMojomNameForKind(typename)
                for typename in
                self.module.structs + all_enums + self.module.unions)
    for typename, typemap in self.typemap.items():
      if typename in types:
        headers.update(typemap.get("public_headers", []))
    return sorted(headers)

  def _GetDirectlyUsedKinds(self):
    for struct in self.module.structs + self.module.unions:
      for field in struct.fields:
        yield field.kind

    for interface in self.module.interfaces:
      for method in interface.methods:
        for param in method.parameters + (method.response_parameters or []):
          yield param.kind

  def _GetJinjaExports(self):
    all_enums = list(self.module.enums)
    for struct in self.module.structs:
      all_enums.extend(struct.enums)
    for interface in self.module.interfaces:
      all_enums.extend(interface.enums)

    return {
      "all_enums": all_enums,
      "disallow_interfaces": self.disallow_interfaces,
      "disallow_native_types": self.disallow_native_types,
      "enums": self.module.enums,
      "export_attribute": self.export_attribute,
      "export_header": self.export_header,
      "extra_public_headers": self._GetExtraPublicHeaders(),
      "extra_traits_headers": self._GetExtraTraitsHeaders(),
      "for_blink": self.for_blink,
      "imports": self.module.imports,
      "interfaces": self.module.interfaces,
      "kinds": self.module.kinds,
      "module": self.module,
      "namespace": self.module.namespace,
      "namespaces_as_array": NamespaceToArray(self.module.namespace),
      "structs": self.module.structs,
      "support_lazy_serialization": self.support_lazy_serialization,
      "unions": self.module.unions,
      "use_once_callback": self.use_once_callback,
      "variant": self.variant,
    }

  @staticmethod
  def GetTemplatePrefix():
    return "cpp_templates"

  def GetFilters(self):
    cpp_filters = {
      "all_enum_values": AllEnumValues,
      "constant_value": self._ConstantValue,
      "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
      "contains_move_only_members": self._ContainsMoveOnlyMembers,
      "cpp_data_view_type": self._GetCppDataViewType,
      "cpp_field_type": self._GetCppFieldType,
      "cpp_union_field_type": self._GetCppUnionFieldType,
      "cpp_pod_type": GetCppPodType,
      "cpp_union_getter_return_type": self._GetUnionGetterReturnType,
      "cpp_union_trait_getter_return_type": self._GetUnionTraitGetterReturnType,
      "cpp_wrapper_call_type": self._GetCppWrapperCallType,
      "cpp_wrapper_param_type": self._GetCppWrapperParamType,
      "cpp_wrapper_param_type_new": self._GetCppWrapperParamTypeNew,
      "cpp_wrapper_type": self._GetCppWrapperType,
      "default_value": self._DefaultValue,
      "expression_to_text": self._ExpressionToText,
      "format_constant_declaration": self._FormatConstantDeclaration,
      "get_container_validate_params_ctor_args":
          self._GetContainerValidateParamsCtorArgs,
      "get_name_for_kind": self._GetNameForKind,
      "get_pad": pack.GetPad,
      "get_qualified_name_for_kind": self._GetQualifiedNameForKind,
      "has_callbacks": mojom.HasCallbacks,
      "has_sync_methods": mojom.HasSyncMethods,
      "method_supports_lazy_serialization":
          self._MethodSupportsLazySerialization,
      "requires_context_for_data_view": RequiresContextForDataView,
      "should_inline": ShouldInlineStruct,
      "should_inline_union": ShouldInlineUnion,
      "is_array_kind": mojom.IsArrayKind,
      "is_enum_kind": mojom.IsEnumKind,
      "is_integral_kind": mojom.IsIntegralKind,
      "is_interface_kind": mojom.IsInterfaceKind,
      "is_native_only_kind": IsNativeOnlyKind,
      "is_any_handle_kind": mojom.IsAnyHandleKind,
      "is_any_interface_kind": mojom.IsAnyInterfaceKind,
      "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
      "is_associated_kind": mojom.IsAssociatedKind,
      "is_hashable": self._IsHashableKind,
      "is_map_kind": mojom.IsMapKind,
      "is_nullable_kind": mojom.IsNullableKind,
      "is_object_kind": mojom.IsObjectKind,
      "is_reference_kind": mojom.IsReferenceKind,
      "is_string_kind": mojom.IsStringKind,
      "is_struct_kind": mojom.IsStructKind,
      "is_typemapped_kind": self._IsTypemappedKind,
      "is_union_kind": mojom.IsUnionKind,
      "passes_associated_kinds": mojom.PassesAssociatedKinds,
      "struct_constructors": self._GetStructConstructors,
      "under_to_camel": generator.ToCamel,
      "unmapped_type_for_serializer": self._GetUnmappedTypeForSerializer,
      "use_custom_serializer": UseCustomSerializer,
      "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum,
    }
    return cpp_filters

  @UseJinja("module.h.tmpl")
  def _GenerateModuleHeader(self):
    return self._GetJinjaExports()

  @UseJinja("module.cc.tmpl")
  def _GenerateModuleSource(self):
    return self._GetJinjaExports()

  @UseJinja("module-shared.h.tmpl")
  def _GenerateModuleSharedHeader(self):
    return self._GetJinjaExports()

  @UseJinja("module-shared-internal.h.tmpl")
  def _GenerateModuleSharedInternalHeader(self):
    return self._GetJinjaExports()

  @UseJinja("module-shared-message-ids.h.tmpl")
  def _GenerateModuleSharedMessageIdsHeader(self):
    return self._GetJinjaExports()

  @UseJinja("module-shared.cc.tmpl")
  def _GenerateModuleSharedSource(self):
    return self._GetJinjaExports()

  def GenerateFiles(self, args):
    self.module.Stylize(generator.Stylizer())

    if self.generate_non_variant_code:
      if self.generate_message_ids:
        self.Write(self._GenerateModuleSharedMessageIdsHeader(),
           "%s-shared-message-ids.h" % self.module.path)
      else:
        self.Write(self._GenerateModuleSharedHeader(),
                   "%s-shared.h" % self.module.path)
        self.Write(self._GenerateModuleSharedInternalHeader(),
                   "%s-shared-internal.h" % self.module.path)
        self.Write(self._GenerateModuleSharedSource(),
                   "%s-shared.cc" % self.module.path)
    else:
      suffix = "-%s" % self.variant if self.variant else ""
      self.Write(self._GenerateModuleHeader(),
                 "%s%s.h" % (self.module.path, suffix))
      self.Write(self._GenerateModuleSource(),
                 "%s%s.cc" % (self.module.path, suffix))

  def _ConstantValue(self, constant):
    return self._ExpressionToText(constant.value, kind=constant.kind)

  def _DefaultValue(self, field):
    if not field.default:
      return ""

    if mojom.IsStructKind(field.kind):
      assert field.default == "default"
      if self._IsTypemappedKind(field.kind):
        return ""
      return "%s::New()" % self._GetNameForKind(field.kind)

    expression = self._ExpressionToText(field.default, kind=field.kind)
    if mojom.IsEnumKind(field.kind) and self._IsTypemappedKind(field.kind):
      expression = "mojo::internal::ConvertEnumValue<%s, %s>(%s)" % (
          self._GetNameForKind(field.kind), self._GetCppWrapperType(field.kind),
          expression)
    return expression

  def _GetNameForKind(self, kind, internal=False, flatten_nested_kind=False,
                      add_same_module_namespaces=False):
    return _NameFormatter(kind, self.variant).FormatForCpp(
        internal=internal, flatten_nested_kind=flatten_nested_kind,
        omit_namespace_for_module = (None if add_same_module_namespaces
                                          else self.module))

  def _GetQualifiedNameForKind(self, kind, internal=False,
                               flatten_nested_kind=False, include_variant=True):
    return _NameFormatter(
        kind, self.variant if include_variant else None).FormatForCpp(
            internal=internal, flatten_nested_kind=flatten_nested_kind)

  def _GetFullMojomNameForKind(self, kind):
    return _NameFormatter(kind, self.variant).FormatForMojom()

  def _IsTypemappedKind(self, kind):
    return hasattr(kind, "name") and \
        self._GetFullMojomNameForKind(kind) in self.typemap

  def _IsHashableKind(self, kind):
    """Check if the kind can be hashed.

    Args:
      kind: {Kind} The kind to check.

    Returns:
      {bool} True if a value of this kind can be hashed.
    """
    checked = set()
    def Check(kind):
      if kind.spec in checked:
        return True
      checked.add(kind.spec)
      if mojom.IsNullableKind(kind):
        return False
      elif mojom.IsStructKind(kind):
        if kind.native_only:
          return False
        if (self._IsTypemappedKind(kind) and
            not self.typemap[self._GetFullMojomNameForKind(kind)]["hashable"]):
          return False
        return all(Check(field.kind) for field in kind.fields)
      elif mojom.IsEnumKind(kind):
        return not self._IsTypemappedKind(kind) or self.typemap[
            self._GetFullMojomNameForKind(kind)]["hashable"]
      elif mojom.IsUnionKind(kind):
        return all(Check(field.kind) for field in kind.fields)
      elif mojom.IsAnyHandleKind(kind):
        return False
      elif mojom.IsAnyInterfaceKind(kind):
        return False
      # TODO(crbug.com/735301): Arrays and maps could be made hashable. We just
      # don't have a use case yet.
      elif mojom.IsArrayKind(kind):
        return False
      elif mojom.IsMapKind(kind):
        return False
      else:
        return True
    return Check(kind)

  def _GetNativeTypeName(self, typemapped_kind):
    return self.typemap[self._GetFullMojomNameForKind(typemapped_kind)][
        "typename"]

  def _FormatConstantDeclaration(self, constant, nested=False):
    if mojom.IsStringKind(constant.kind):
      if nested:
        return "const char %s[]" % constant.name
      return "%sextern const char %s[]" % \
          ((self.export_attribute + " ") if self.export_attribute else "",
           constant.name)
    return "constexpr %s %s = %s" % (
        GetCppPodType(constant.kind), constant.name,
        self._ConstantValue(constant))

  def _GetCppWrapperType(self, kind, add_same_module_namespaces=False):
    def _AddOptional(type_name):
      return "base::Optional<%s>" % type_name

    if self._IsTypemappedKind(kind):
      type_name = self._GetNativeTypeName(kind)
      if (mojom.IsNullableKind(kind) and
          not self.typemap[self._GetFullMojomNameForKind(kind)][
             "nullable_is_same_type"]):
        type_name = _AddOptional(type_name)
      return type_name
    if mojom.IsEnumKind(kind):
      return self._GetNameForKind(
          kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
      return "%sPtr" % self._GetNameForKind(
          kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsArrayKind(kind):
      pattern = "WTF::Vector<%s>" if self.for_blink else "std::vector<%s>"
      if mojom.IsNullableKind(kind):
        pattern = _AddOptional(pattern)
      return pattern % self._GetCppWrapperType(
          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsMapKind(kind):
      pattern = ("WTF::HashMap<%s, %s>" if self.for_blink else
                 "base::flat_map<%s, %s>")
      if mojom.IsNullableKind(kind):
        pattern = _AddOptional(pattern)
      return pattern % (
          self._GetCppWrapperType(
              kind.key_kind,
              add_same_module_namespaces=add_same_module_namespaces),
          self._GetCppWrapperType(
              kind.value_kind,
              add_same_module_namespaces=add_same_module_namespaces))
    if mojom.IsInterfaceKind(kind):
      return "%sPtrInfo" % self._GetNameForKind(
          kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsInterfaceRequestKind(kind):
      return "%sRequest" % self._GetNameForKind(
          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsAssociatedInterfaceKind(kind):
      return "%sAssociatedPtrInfo" % self._GetNameForKind(
          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsAssociatedInterfaceRequestKind(kind):
      return "%sAssociatedRequest" % self._GetNameForKind(
          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
    if mojom.IsStringKind(kind):
      if self.for_blink:
        return "WTF::String"
      type_name = "std::string"
      return (_AddOptional(type_name) if mojom.IsNullableKind(kind)
                                      else type_name)
    if mojom.IsGenericHandleKind(kind):
      return "mojo::ScopedHandle"
    if mojom.IsDataPipeConsumerKind(kind):
      return "mojo::ScopedDataPipeConsumerHandle"
    if mojom.IsDataPipeProducerKind(kind):
      return "mojo::ScopedDataPipeProducerHandle"
    if mojom.IsMessagePipeKind(kind):
      return "mojo::ScopedMessagePipeHandle"
    if mojom.IsSharedBufferKind(kind):
      return "mojo::ScopedSharedBufferHandle"
    if not kind in _kind_to_cpp_type:
      raise Exception("Unrecognized kind %s" % kind.spec)
    return _kind_to_cpp_type[kind]

  def _IsMoveOnlyKind(self, kind):
    if self._IsTypemappedKind(kind):
      if mojom.IsEnumKind(kind):
        return False
      return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"]
    if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
      return True
    if mojom.IsArrayKind(kind):
      return self._IsMoveOnlyKind(kind.kind)
    if mojom.IsMapKind(kind):
      return (self._IsMoveOnlyKind(kind.value_kind) or
              self._IsMoveOnlyKind(kind.key_kind))
    if mojom.IsAnyHandleOrInterfaceKind(kind):
      return True
    return False

  def _IsCopyablePassByValue(self, kind):
    if not self._IsTypemappedKind(kind):
      return False
    return self.typemap[self._GetFullMojomNameForKind(kind)][
        "copyable_pass_by_value"]

  def _ShouldPassParamByValue(self, kind):
    return ((not mojom.IsReferenceKind(kind)) or self._IsMoveOnlyKind(kind) or
        self._IsCopyablePassByValue(kind))

  def _GetCppWrapperCallType(self, kind):
    # TODO: Remove this once interfaces are always passed as PtrInfo.
    if mojom.IsInterfaceKind(kind):
      return "%sPtr" % self._GetNameForKind(kind)
    return self._GetCppWrapperType(kind)

  def _GetCppWrapperParamType(self, kind):
    # TODO: Remove all usage of this method in favor of
    # _GetCppWrapperParamTypeNew. This requires all generated code which passes
    # interface handles to use PtrInfo instead of Ptr.
    if mojom.IsInterfaceKind(kind):
      return "%sPtr" % self._GetNameForKind(kind)
    cpp_wrapper_type = self._GetCppWrapperType(kind)
    return (cpp_wrapper_type if self._ShouldPassParamByValue(kind)
                             else "const %s&" % cpp_wrapper_type)

  def _GetCppWrapperParamTypeNew(self, kind):
    cpp_wrapper_type = self._GetCppWrapperType(kind)
    return (cpp_wrapper_type if self._ShouldPassParamByValue(kind)
                             else "const %s&" % cpp_wrapper_type)

  def _GetCppFieldType(self, kind):
    if mojom.IsStructKind(kind):
      return ("mojo::internal::Pointer<%s>" %
          self._GetNameForKind(kind, internal=True))
    if mojom.IsUnionKind(kind):
      return "%s" % self._GetNameForKind(kind, internal=True)
    if mojom.IsArrayKind(kind):
      return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" %
              self._GetCppFieldType(kind.kind))
    if mojom.IsMapKind(kind):
      return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" %
              (self._GetCppFieldType(kind.key_kind),
               self._GetCppFieldType(kind.value_kind)))
    if mojom.IsInterfaceKind(kind):
      return "mojo::internal::Interface_Data"
    if mojom.IsInterfaceRequestKind(kind):
      return "mojo::internal::Handle_Data"
    if mojom.IsAssociatedInterfaceKind(kind):
      return "mojo::internal::AssociatedInterface_Data"
    if mojom.IsAssociatedInterfaceRequestKind(kind):
      return "mojo::internal::AssociatedEndpointHandle_Data"
    if mojom.IsEnumKind(kind):
      return "int32_t"
    if mojom.IsStringKind(kind):
      return "mojo::internal::Pointer<mojo::internal::String_Data>"
    if mojom.IsAnyHandleKind(kind):
      return "mojo::internal::Handle_Data"
    return _kind_to_cpp_type[kind]

  def _GetCppUnionFieldType(self, kind):
    if mojom.IsUnionKind(kind):
      return ("mojo::internal::Pointer<%s>" %
                  self._GetNameForKind(kind, internal=True))
    return self._GetCppFieldType(kind)

  def _GetUnionGetterReturnType(self, kind):
    if mojom.IsReferenceKind(kind):
      return "%s&" % self._GetCppWrapperType(kind)
    return self._GetCppWrapperType(kind)

  def _GetUnionTraitGetterReturnType(self, kind):
    """Get field type used in UnionTraits template specialization.

    The type may be qualified as UnionTraits specializations live outside the
    namespace where e.g. structs are defined.

    Args:
      kind: {Kind} The type of the field.

    Returns:
      {str} The C++ type to use for the field.
    """
    if mojom.IsReferenceKind(kind):
      return "%s&" % self._GetCppWrapperType(kind,
                                             add_same_module_namespaces=True)
    return self._GetCppWrapperType(kind, add_same_module_namespaces=True)

  def _KindMustBeSerialized(self, kind, processed_kinds=None):
    if not processed_kinds:
      processed_kinds = set()
    if kind in processed_kinds:
      return False

    if (self._IsTypemappedKind(kind) and
        self.typemap[self._GetFullMojomNameForKind(kind)]["force_serialize"]):
      return True

    processed_kinds.add(kind)

    if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
      return any(self._KindMustBeSerialized(field.kind,
                                            processed_kinds=processed_kinds)
                 for field in kind.fields)

    return False

  def _MethodSupportsLazySerialization(self, method):
    if not self.support_lazy_serialization:
      return False

    # TODO(crbug.com/753433): Support lazy serialization for methods which pass
    # associated handles.
    if mojom.MethodPassesAssociatedKinds(method):
      return False

    return not any(self._KindMustBeSerialized(param.kind) for param in
                   method.parameters + (method.response_parameters or []))

  def _TranslateConstants(self, token, kind):
    if isinstance(token, mojom.NamedValue):
      return self._GetNameForKind(token, flatten_nested_kind=True)

    if isinstance(token, mojom.BuiltinValue):
      if token.value == "double.INFINITY":
        return "std::numeric_limits<double>::infinity()"
      if token.value == "float.INFINITY":
        return "std::numeric_limits<float>::infinity()"
      if token.value == "double.NEGATIVE_INFINITY":
        return "-std::numeric_limits<double>::infinity()"
      if token.value == "float.NEGATIVE_INFINITY":
        return "-std::numeric_limits<float>::infinity()"
      if token.value == "double.NAN":
        return "std::numeric_limits<double>::quiet_NaN()"
      if token.value == "float.NAN":
        return "std::numeric_limits<float>::quiet_NaN()"

    if (kind is not None and mojom.IsFloatKind(kind)):
        return token if token.isdigit() else token + "f";

    # Per C++11, 2.14.2, the type of an integer literal is the first of the
    # corresponding list in Table 6 in which its value can be represented. In
    # this case, the list for decimal constants with no suffix is:
    #   int, long int, long long int
    # The standard considers a program ill-formed if it contains an integer
    # literal that cannot be represented by any of the allowed types.
    #
    # As it turns out, MSVC doesn't bother trying to fall back to long long int,
    # so the integral constant -2147483648 causes it grief: it decides to
    # represent 2147483648 as an unsigned integer, and then warns that the unary
    # minus operator doesn't make sense on unsigned types. Doh!
    if kind == mojom.INT32 and token == "-2147483648":
      return "(-%d - 1) /* %s */" % (
          2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618")

    return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, ""))

  def _ExpressionToText(self, value, kind=None):
    return self._TranslateConstants(value, kind)

  def _ContainsMoveOnlyMembers(self, struct):
    for field in struct.fields:
      if self._IsMoveOnlyKind(field.kind):
        return True
    return False

  def _GetStructConstructors(self, struct):
    """Returns a list of constructors for a struct.

    Params:
      struct: {Struct} The struct to return constructors for.

    Returns:
      {[StructConstructor]} A list of StructConstructors that should be
      generated for |struct|.
    """
    if not mojom.IsStructKind(struct):
      raise TypeError
    # Types that are neither copyable nor movable can't be passed to a struct
    # constructor so only generate a default constructor.
    if any(self._IsTypemappedKind(field.kind) and self.typemap[
        self._GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"]
           for field in struct.fields):
      return [StructConstructor(struct.fields, [])]

    param_counts = [0]
    for version in struct.versions:
      if param_counts[-1] != version.num_fields:
        param_counts.append(version.num_fields)

    ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal if field.ordinal != None else -1)
    return (StructConstructor(struct.fields, ordinal_fields[:param_count])
            for param_count in param_counts)

  def _GetContainerValidateParamsCtorArgs(self, kind):
    if mojom.IsStringKind(kind):
      expected_num_elements = 0
      element_is_nullable = False
      key_validate_params = "nullptr"
      element_validate_params = "nullptr"
      enum_validate_func = "nullptr"
    elif mojom.IsMapKind(kind):
      expected_num_elements = 0
      element_is_nullable = False
      key_validate_params = self._GetNewContainerValidateParams(mojom.Array(
          kind=kind.key_kind))
      element_validate_params = self._GetNewContainerValidateParams(mojom.Array(
          kind=kind.value_kind))
      enum_validate_func = "nullptr"
    else:  # mojom.IsArrayKind(kind)
      expected_num_elements = generator.ExpectedArraySize(kind) or 0
      element_is_nullable = mojom.IsNullableKind(kind.kind)
      key_validate_params = "nullptr"
      element_validate_params = self._GetNewContainerValidateParams(kind.kind)
      if mojom.IsEnumKind(kind.kind):
        enum_validate_func = ("%s::Validate" %
            self._GetQualifiedNameForKind(kind.kind, internal=True,
                                          flatten_nested_kind=True))
      else:
        enum_validate_func = "nullptr"

    if enum_validate_func == "nullptr":
      if key_validate_params == "nullptr":
        return "%d, %s, %s" % (expected_num_elements,
                               "true" if element_is_nullable else "false",
                               element_validate_params)
      else:
        return "%s, %s" % (key_validate_params, element_validate_params)
    else:
      return "%d, %s" % (expected_num_elements, enum_validate_func)

  def _GetNewContainerValidateParams(self, kind):
    if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and
        not mojom.IsStringKind(kind)):
      return "nullptr"

    return "new mojo::internal::ContainerValidateParams(%s)" % (
        self._GetContainerValidateParamsCtorArgs(kind))

  def _GetCppDataViewType(self, kind, qualified=False):
    def _GetName(input_kind):
      return _NameFormatter(input_kind, None).FormatForCpp(
          omit_namespace_for_module=(None if qualified else self.module),
          flatten_nested_kind=True)

    if mojom.IsEnumKind(kind):
      return _GetName(kind)
    if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
      return "%sDataView" % _GetName(kind)
    if mojom.IsArrayKind(kind):
      return "mojo::ArrayDataView<%s>" % (
          self._GetCppDataViewType(kind.kind, qualified))
    if mojom.IsMapKind(kind):
      return ("mojo::MapDataView<%s, %s>" % (
          self._GetCppDataViewType(kind.key_kind, qualified),
          self._GetCppDataViewType(kind.value_kind, qualified)))
    if mojom.IsStringKind(kind):
      return "mojo::StringDataView"
    if mojom.IsInterfaceKind(kind):
      return "%sPtrDataView" % _GetName(kind)
    if mojom.IsInterfaceRequestKind(kind):
      return "%sRequestDataView" % _GetName(kind.kind)
    if mojom.IsAssociatedInterfaceKind(kind):
      return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind)
    if mojom.IsAssociatedInterfaceRequestKind(kind):
      return "%sAssociatedRequestDataView" % _GetName(kind.kind)
    if mojom.IsGenericHandleKind(kind):
      return "mojo::ScopedHandle"
    if mojom.IsDataPipeConsumerKind(kind):
      return "mojo::ScopedDataPipeConsumerHandle"
    if mojom.IsDataPipeProducerKind(kind):
      return "mojo::ScopedDataPipeProducerHandle"
    if mojom.IsMessagePipeKind(kind):
      return "mojo::ScopedMessagePipeHandle"
    if mojom.IsSharedBufferKind(kind):
      return "mojo::ScopedSharedBufferHandle"
    return _kind_to_cpp_type[kind]

  def _GetUnmappedTypeForSerializer(self, kind):
    return self._GetCppDataViewType(kind, qualified=True)
