# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Intermediate representation (IR) for Emboss.

This is limited to purely data and type annotations.
"""

import dataclasses
import enum
import sys
from typing import ClassVar, Optional

from compiler.util import ir_data_fields


@dataclasses.dataclass
class Message:
  """Base class for IR data objects.

  Historically protocol buffers were used for serializing this data which has
  led to some legacy naming conventions and references. In particular this
  class is named `Message` in the sense of a protocol buffer message,
  indicating that it is intended to just be data that is used by other higher
  level services.

  There are some other legacy idioms leftover from the protocol buffer-based
  definition such as support for "oneof" and optional fields.
  """

  IR_DATACLASS: ClassVar[object] = object()
  field_specs: ClassVar[ir_data_fields.FilteredIrFieldSpecs]

  def __post_init__(self):
    """Called by dataclass subclasses after init.

    Post-processes any lists passed in to use our custom list type.
    """
    # Convert any lists passed in to CopyValuesList
    for spec in self.field_specs.sequence_field_specs:
      cur_val = getattr(self, spec.name)
      if isinstance(cur_val, ir_data_fields.TemporaryCopyValuesList):
        copy_val = cur_val.temp_list
      else:
        copy_val = ir_data_fields.CopyValuesList(spec.data_type)
        if cur_val:
          copy_val.shallow_copy(cur_val)
      setattr(self, spec.name, copy_val)

  # This hook adds a 15% overhead to end-to-end code generation in some cases
  # so we guard it in a `__debug__` block. Users can opt-out of this check by
  # running python with the `-O` flag, ie: `python3 -O ./embossc`.
  if __debug__:
    def __setattr__(self, name: str, value) -> None:
      """Debug-only hook that adds basic type checking for ir_data fields."""
      if spec := self.field_specs.all_field_specs.get(name):
        if not (
            # Check if it's the expected type
            isinstance(value, spec.data_type) or
            # Oneof fields are a special case
            spec.is_oneof or
            # Optional fields can be set to None
            (spec.container is ir_data_fields.FieldContainer.OPTIONAL and
                 value is None) or
            # Sequences can be a few variants of lists
            (spec.is_sequence and
                 isinstance(value, (
                    list, ir_data_fields.TemporaryCopyValuesList,
                    ir_data_fields.CopyValuesList))) or
            # An enum value can be an int
            (spec.is_enum and isinstance(value, int))):
          raise AttributeError(
            f"Cannot set {value} (type {value.__class__}) for type"
             "{spec.data_type}")
      object.__setattr__(self, name, value)

  # Non-PEP8 name to mimic the Google Protobuf interface.
  def HasField(self, name):  # pylint:disable=invalid-name
    """Indicates if this class has the given field defined and it is set."""
    return getattr(self, name, None) is not None

  # Non-PEP8 name to mimic the Google Protobuf interface.
  def WhichOneof(self, oneof_name):  # pylint:disable=invalid-name
    """Indicates which field has been set for the oneof value.

    Returns None if no field has been set.
    """
    for field_name, oneof in self.field_specs.oneof_mappings:
      if oneof == oneof_name and self.HasField(field_name):
        return field_name
    return None


################################################################################
# From here to the end of the file are actual structure definitions.


@dataclasses.dataclass
class Position(Message):
  """A zero-width position within a source file."""

  line: int = 0
  """Line (starts from 1)."""
  column: int = 0
  """Column (starts from 1)."""


@dataclasses.dataclass
class Location(Message):
  """A half-open start:end range within a source file."""

  start: Optional[Position] = None
  """Beginning of the range"""
  end: Optional[Position] = None
  """One column past the end of the range."""

  is_disjoint_from_parent: Optional[bool] = None
  """True if this Location is outside of the parent object's Location."""

  is_synthetic: Optional[bool] = None
  """True if this Location's parent was synthesized, and does not directly
  appear in the source file.

  The Emboss front end uses this field to cull
  irrelevant error messages.
  """


@dataclasses.dataclass
class Word(Message):
  """IR for a bare word in the source file.

  This is used in NameDefinitions and References.
  """

  text: Optional[str] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class String(Message):
  """IR for a string in the source file."""

  text: Optional[str] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Documentation(Message):
  text: Optional[str] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class BooleanConstant(Message):
  """IR for a boolean constant."""

  value: Optional[bool] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Empty(Message):
  """Placeholder message for automatic element counts for arrays."""

  source_location: Optional[Location] = None


@dataclasses.dataclass
class NumericConstant(Message):
  """IR for any numeric constant."""

  # Numeric constants are stored as decimal strings; this is the simplest way
  # to store the full -2**63..+2**64 range.
  #
  # TODO(bolms): switch back to int, and just use strings during
  # serialization, now that we're free of proto.
  value: Optional[str] = None
  source_location: Optional[Location] = None


class FunctionMapping(int, enum.Enum):
  """Enum of supported function types"""

  UNKNOWN = 0
  ADDITION = 1
  """`+`"""
  SUBTRACTION = 2
  """`-`"""
  MULTIPLICATION = 3
  """`*`"""
  EQUALITY = 4
  """`==`"""
  INEQUALITY = 5
  """`!=`"""
  AND = 6
  """`&&`"""
  OR = 7
  """`||`"""
  LESS = 8
  """`<`"""
  LESS_OR_EQUAL = 9
  """`<=`"""
  GREATER = 10
  """`>`"""
  GREATER_OR_EQUAL = 11
  """`>=`"""
  CHOICE = 12
  """`?:`"""
  MAXIMUM = 13
  """`$max()`"""
  PRESENCE = 14
  """`$present()`"""
  UPPER_BOUND = 15
  """`$upper_bound()`"""
  LOWER_BOUND = 16
  """`$lower_bound()`"""


@dataclasses.dataclass
class Function(Message):
  """IR for a single function (+, -, *, ==, $max, etc.) in an expression."""

  function: Optional[FunctionMapping] = None
  args: list["Expression"] = ir_data_fields.list_field(lambda: Expression)
  function_name: Optional[Word] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class CanonicalName(Message):
  """CanonicalName is the unique, absolute name for some object.

  A CanonicalName is the unique, absolute name for some object (Type, field,
  etc.) in the IR.  It is used both in the definitions of objects ("struct
  Foo"), and in references to objects (a field of type "Foo").
  """

  module_file: str = ir_data_fields.str_field()
  """The module_file is the Module.source_file_name of the Module in which this
  object's definition appears.

  Note that the Prelude always has a Module.source_file_name of "", and thus
  references to Prelude names will have module_file == "".
  """

  object_path: list[str] = ir_data_fields.list_field(str)
  """The object_path is the canonical path to the object definition within its
  module file.

  For example, the field "bar" would have an object path of
  ["Foo", "bar"]:

  struct Foo:
    0:3  UInt  bar


  The enumerated name "BOB" would have an object path of ["Baz", "Qux",
  "BOB"]:

  struct Baz:
    0:3  Qux   qux

    enum Qux:
      BOB = 0
  """


@dataclasses.dataclass
class NameDefinition(Message):
  """NameDefinition is IR for the name of an object, within the object.

  That is, a TypeDefinition or Field will hold a NameDefinition as its
  name.
  """

  name: Optional[Word] = None
  """The name, as directly generated from the source text.

  name.text will match the last element of canonical_name.object_path. Note
  that in some cases, the exact string in name.text may not appear in the
  source text.
  """

  canonical_name: Optional[CanonicalName] = None
  """The CanonicalName that will appear in References.
  This field is technically redundant: canonical_name.module_file should always
  match the source_file_name of the enclosing Module, and
  canonical_name.object_path should always match the names of parent nodes.
  """

  is_anonymous: Optional[bool] = None
  """If true, indicates that this is an automatically-generated name, which
  should not be visible outside of its immediate namespace.
  """

  source_location: Optional[Location] = None
  """The location of this NameDefinition in source code."""


@dataclasses.dataclass
class Reference(Message):
  """A Reference holds the canonical name of something defined elsewhere.

  For example, take this fragment:

   struct Foo:
    0:3  UInt    size (s)
    4:s  Int:8[] payload

  "Foo", "size", and "payload" will become NameDefinitions in their
  corresponding Field and Message IR objects, while "UInt", the second "s",
  and "Int" are References.  Note that the second "s" will have a
  canonical_name.object_path of ["Foo", "size"], not ["Foo", "s"]: the
  Reference always holds the single "true" name of the object, regardless of
  what appears in the .emb.
  """

  canonical_name: Optional[CanonicalName] = None
  """The canonical name of the object being referred to.

  This name should be used to find the object in the IR.
  """

  source_name: list[Word] = ir_data_fields.list_field(Word)
  """The source_name is the name the user entered in the source file.

  The source_name could be either relative or absolute, and may be an alias
  (and thus not match any part of the canonical_name).  Back ends should use
  canonical_name for name lookup, and reserve source_name for error messages.
  """

  is_local_name: Optional[bool] = None
  """If true, then symbol resolution should only look at local names when
  resolving source_name.

  This is used so that the names of inline types aren't "ambiguous" if there
  happens to be another type with the same name at a parent scope.
  """

  # TODO(bolms): Allow absolute paths starting with ".".

  source_location: Optional[Location] = None
  """Note that this is the source_location of the *Reference*, not of the
  object to which it refers.
  """


@dataclasses.dataclass
class FieldReference(Message):
  """IR for a "field" or "field.sub.subsub" reference in an expression.

  The first element of "path" is the "base" field, which should be directly
  readable in the (runtime) context of the expression.  For example:

    struct Foo:
     0:1  UInt      header_size (h)
     0:h  UInt:8[]  header_bytes

  The "h" will translate to ["Foo", "header_size"], which will be the first
  (and in this case only) element of "path".

  Subsequent path elements should be treated as subfields.  For example, in:

    struct Foo:
     struct Sizes:
      0:1  UInt  header_size
      1:2  UInt  body_size
     0                 [+2]                  Sizes     sizes
     0                 [+sizes.header_size]  UInt:8[]  header
     sizes.header_size [+sizes.body_size]    UInt:8[]  body

  The references to "sizes.header_size" will have a path of [["Foo",
  "sizes"], ["Foo", "Sizes", "header_size"]].  Note that each path element is
  a fully-qualified reference; some back ends (C++, Python) may only use the
  last element, while others (C) may use the complete path.

  This representation is a bit awkward, and is fundamentally limited to a
  dotted list of static field names.  It does not allow an expression like
  `array[n]` on the left side of a `.`.  At this point, it is an artifact of
  the era during which I (bolms@) thought I could get away with skipping
  compiler-y things.
  """

  # TODO(bolms): Add composite types to the expression type system, and
  # replace FieldReference with a "member access" Expression kind.  Further,
  # move the symbol resolution for FieldReferences that is currently in
  # symbol_resolver.py into type_check.py.

  # TODO(bolms): Make the above change before declaring the IR to be "stable".

  path: list[Reference] = ir_data_fields.list_field(Reference)
  source_location: Optional[Location] = None


@dataclasses.dataclass
class OpaqueType(Message):
  pass


@dataclasses.dataclass
class IntegerType(Message):
  """Type of an integer expression."""

  # For optimization, the modular congruence of an integer expression is
  # tracked.  This consists of a modulus and a modular_value, such that for
  # all possible values of expression, expression MOD modulus ==
  # modular_value.
  #
  # The modulus may be the special value "infinity" to indicate that the
  # expression's value is exactly modular_value; otherwise, it should be a
  # positive integer.
  #
  # A modulus of 1 places no constraints on the value.
  #
  # The modular_value should always be a nonnegative integer that is smaller
  # than the modulus.
  #
  # Note that this is specifically the *modulus*, which is not equivalent to
  # the value from C's '%' operator when the dividend is negative: in C, -7 %
  # 4 == -3, but the modular_value here would be 1.  Python uses modulus: in
  # Python, -7 % 4 == 1.
  modulus: Optional[str] = None
  """The modulus portion of the modular congruence of an integer expression.

  The modulus may be the special value "infinity" to indicate that the
  expression's value is exactly modular_value; otherwise, it should be a
  positive integer.

  A modulus of 1 places no constraints on the value.
  """
  modular_value: Optional[str] = None
  """ The modular_value portion of the modular congruence of an integer expression.

  The modular_value should always be a nonnegative integer that is smaller
  than the modulus.
  """

  # The minimum and maximum values of an integer are tracked and checked so
  # that Emboss can implement reliable arithmetic with no operations
  # overflowing either 64-bit unsigned or 64-bit signed 2's-complement
  # integers.
  #
  # Note that constant subexpressions are allowed to overflow, as long as the
  # final, computed constant value of the subexpression fits in a 64-bit
  # value.
  #
  # The minimum_value may take the value "-infinity", and the maximum_value
  # may take the value "infinity".  These sentinel values indicate that
  # Emboss has no bound information for the Expression, and therefore the
  # Expression may only be evaluated during compilation; the back end should
  # never need to compile such an expression into the target language (e.g.,
  # C++).
  minimum_value: Optional[str] = None
  maximum_value: Optional[str] = None


@dataclasses.dataclass
class BooleanType(Message):
  value: Optional[bool] = None


@dataclasses.dataclass
class EnumType(Message):
  name: Optional[Reference] = None
  value: Optional[str] = None


@dataclasses.dataclass
class ExpressionType(Message):
  opaque: Optional[OpaqueType] = ir_data_fields.oneof_field("type")
  integer: Optional[IntegerType] = ir_data_fields.oneof_field("type")
  boolean: Optional[BooleanType] = ir_data_fields.oneof_field("type")
  enumeration: Optional[EnumType] = ir_data_fields.oneof_field("type")


@dataclasses.dataclass
class Expression(Message):
  """IR for an expression.

  An Expression is a potentially-recursive data structure.  It can either
  represent a leaf node (constant or reference) or an operation combining
  other Expressions (function).
  """

  constant: Optional[NumericConstant] = ir_data_fields.oneof_field("expression")
  constant_reference: Optional[Reference] = ir_data_fields.oneof_field(
      "expression"
  )
  function: Optional[Function] = ir_data_fields.oneof_field("expression")
  field_reference: Optional[FieldReference] = ir_data_fields.oneof_field(
      "expression"
  )
  boolean_constant: Optional[BooleanConstant] = ir_data_fields.oneof_field(
      "expression"
  )
  builtin_reference: Optional[Reference] = ir_data_fields.oneof_field(
      "expression"
  )

  type: Optional[ExpressionType] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class ArrayType(Message):
  """IR for an array type ("Int:8[12]" or "Message[2]" or "UInt[3][2]")."""

  base_type: Optional["Type"] = None

  element_count: Optional[Expression] = ir_data_fields.oneof_field("size")
  automatic: Optional[Empty] = ir_data_fields.oneof_field("size")

  source_location: Optional[Location] = None


@dataclasses.dataclass
class AtomicType(Message):
  """IR for a non-array type ("UInt" or "Foo(Version.SIX)")."""

  reference: Optional[Reference] = None
  runtime_parameter: list[Expression] = ir_data_fields.list_field(Expression)
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Type(Message):
  """IR for a type reference ("UInt", "Int:8[12]", etc.)."""

  atomic_type: Optional[AtomicType] = ir_data_fields.oneof_field("type")
  array_type: Optional[ArrayType] = ir_data_fields.oneof_field("type")

  size_in_bits: Optional[Expression] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class AttributeValue(Message):
  """IR for a attribute value."""

  # TODO(bolms): Make String a type of Expression, and replace
  # AttributeValue with Expression.
  expression: Optional[Expression] = ir_data_fields.oneof_field("value")
  string_constant: Optional[String] = ir_data_fields.oneof_field("value")

  source_location: Optional[Location] = None


@dataclasses.dataclass
class Attribute(Message):
  """IR for a [name = value] attribute."""

  name: Optional[Word] = None
  value: Optional[AttributeValue] = None
  back_end: Optional[Word] = None
  is_default: Optional[bool] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class WriteTransform(Message):
  """IR which defines an expression-based virtual field write scheme.

  E.g., for a virtual field like `x_plus_one`:

    struct Foo:
     0 [+1]  UInt  x
     let x_plus_one = x + 1

  ... the `WriteMethod` would be `transform`, with `$logical_value - 1` for
  `function_body` and `x` for `destination`.
  """

  function_body: Optional[Expression] = None
  destination: Optional[FieldReference] = None


@dataclasses.dataclass
class WriteMethod(Message):
  """IR which defines the method used for writing to a virtual field."""

  physical: Optional[bool] = ir_data_fields.oneof_field("method")
  """A physical Field can be written directly."""

  read_only: Optional[bool] = ir_data_fields.oneof_field("method")
  """A read_only Field cannot be written."""

  alias: Optional[FieldReference] = ir_data_fields.oneof_field("method")
  """An alias is a direct, untransformed forward of another field; it can be
  implemented by directly returning a reference to the aliased field.

  Aliases are the only kind of virtual field that may have an opaque type.
  """

  transform: Optional[WriteTransform] = ir_data_fields.oneof_field("method")
  """A transform is a way of turning a logical value into a value which should
  be written to another field.

  A virtual field like `let y = x + 1` would
  have a transform WriteMethod to subtract 1 from the new `y` value, and
  write that to `x`.
  """


@dataclasses.dataclass
class FieldLocation(Message):
  """IR for a field location."""

  start: Optional[Expression] = None
  size: Optional[Expression] = None
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Field(Message):  # pylint:disable=too-many-instance-attributes
  """IR for a field in a struct definition.

  There are two kinds of Field: physical fields have location and (physical)
  type; virtual fields have read_transform.  Although there are differences,
  in many situations physical and virtual fields are treated the same way,
  and they can be freely intermingled in the source file.
  """

  location: Optional[FieldLocation] = None
  """The physical location of the field."""
  type: Optional[Type] = None
  """The physical type of the field."""

  read_transform: Optional[Expression] = None
  """The value of a virtual field."""

  write_method: Optional[WriteMethod] = None
  """How this virtual field should be written."""

  name: Optional[NameDefinition] = None
  """The name of the field."""
  abbreviation: Optional[Word] = None
  """An optional short name for the field, only visible inside the enclosing bits/struct."""
  attribute: list[Attribute] = ir_data_fields.list_field(Attribute)
  """Field-specific attributes."""
  documentation: list[Documentation] = ir_data_fields.list_field(Documentation)
  """Field-specific documentation."""

  # TODO(bolms): Document conditional fields better, and replace some of this
  # explanation with a reference to the documentation.
  existence_condition: Optional[Expression] = None
  """The field only exists when existence_condition evaluates to true.

  For example:
  ```
  struct Message:
    0 [+4]  UInt         length
    4 [+8]  MessageType  message_type
    if message_type == MessageType.FOO:
      8 [+length]  Foo   foo
    if message_type == MessageType.BAR:
      8 [+length]  Bar   bar
    8+length [+4]  UInt  crc
  ```
  For `length`, `message_type`, and `crc`, existence_condition will be
  `boolean_constant { value: true }`

  For `foo`, existence_condition will be:
  ```
      function { function: EQUALITY
                 args: [reference to message_type]
                 args: { [reference to MessageType.FOO] } }
  ```

  The `bar` field will have a similar existence_condition to `foo`:
  ```
      function { function: EQUALITY
                 args: [reference to message_type]
                 args: { [reference to MessageType.BAR] } }
  ```

  When `message_type` is `MessageType.BAR`, the `Message` struct does not contain
  field `foo`, and vice versa for `message_type == MessageType.FOO` and field
  `bar`: those fields only conditionally exist in the structure.
  """

  source_location: Optional[Location] = None


@dataclasses.dataclass
class Structure(Message):
  """IR for a bits or struct definition."""

  field: list[Field] = ir_data_fields.list_field(Field)

  fields_in_dependency_order: list[int] = ir_data_fields.list_field(int)
  """The fields in `field` are listed in the order they appear in the original
  .emb.

  For text format output, this can lead to poor results.  Take the following
  struct:
  ```
      struct Foo:
        b [+4]  UInt  a
        0 [+4]  UInt  b
  ```
  Here, the location of `a` depends on the current value of `b`.  Because of
  this, if someone calls
  ```
      emboss::UpdateFromText(foo_view, "{ a: 10, b: 4 }");
  ```
  then foo_view will not be updated the way one would expect: if `b`'s value
  was something other than 4 to start with, then `UpdateFromText` will write
  the 10 to some other location, then update `b` to 4.

  To avoid surprises, `emboss::DumpAsText` should return `"{ b: 4, a: 10
  }"`.

  The `fields_in_dependency_order` field provides a permutation of `field`
  such that each field appears after all of its dependencies.  For example,
  `struct Foo`, above, would have `{ 1, 0 }` in
  `fields_in_dependency_order`.

  The exact ordering of `fields_in_dependency_order` is not guaranteed, but
  some effort is made to keep the order close to the order fields are listed
  in the original `.emb` file.  In particular, if the ordering 0, 1, 2, 3,
  ... satisfies dependency ordering, then `fields_in_dependency_order` will
  be `{ 0, 1, 2, 3, ... }`.
  """

  source_location: Optional[Location] = None


@dataclasses.dataclass
class External(Message):
  """IR for an external type declaration."""

  # Externals have no values other than name and attribute list, which are
  # common to all type definitions.

  source_location: Optional[Location] = None


@dataclasses.dataclass
class EnumValue(Message):
  """IR for a single value within an enumerated type."""

  name: Optional[NameDefinition] = None
  """The name of the enum value."""
  value: Optional[Expression] = None
  """The value of the enum value."""
  documentation: list[Documentation] = ir_data_fields.list_field(Documentation)
  """Value-specific documentation."""
  attribute: list[Attribute] = ir_data_fields.list_field(Attribute)
  """Value-specific attributes."""

  source_location: Optional[Location] = None


@dataclasses.dataclass
class Enum(Message):
  """IR for an enumerated type definition."""

  value: list[EnumValue] = ir_data_fields.list_field(EnumValue)
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Import(Message):
  """IR for an import statement in a module."""

  file_name: Optional[String] = None
  """The file to import."""
  local_name: Optional[Word] = None
  """The name to use within this module."""
  source_location: Optional[Location] = None


@dataclasses.dataclass
class RuntimeParameter(Message):
  """IR for a runtime parameter definition."""

  name: Optional[NameDefinition] = None
  """The name of the parameter."""
  type: Optional[ExpressionType] = None
  """The type of the parameter."""

  # TODO(bolms): Actually implement the set builder type notation.
  physical_type_alias: Optional[Type] = None
  """For convenience and readability, physical types may be used in the .emb
  source instead of a full expression type.

  That way, users can write
  something like:
  ```
      struct Foo(version :: UInt:8):
  ```
  instead of:
  ```
      struct Foo(version :: {$int x |: 0 <= x <= 255}):
  ```
  In these cases, physical_type_alias holds the user-supplied type, and type
  is filled in after initial parsing is finished.
  """

  source_location: Optional[Location] = None


class AddressableUnit(int, enum.Enum):
  """The "addressable unit" is the size of the smallest unit that can be read

  from the backing store that this type expects.  For `struct`s, this is
  BYTE; for `enum`s and `bits`, this is BIT, and for `external`s it depends
  on the specific type
  """

  NONE = 0
  BIT = 1
  BYTE = 8


@dataclasses.dataclass
class TypeDefinition(Message):
  """Container IR for a type definition (struct, union, etc.)"""

  external: Optional[External] = ir_data_fields.oneof_field("type")
  enumeration: Optional[Enum] = ir_data_fields.oneof_field("type")
  structure: Optional[Structure] = ir_data_fields.oneof_field("type")

  name: Optional[NameDefinition] = None
  """The name of the type."""
  attribute: list[Attribute] = ir_data_fields.list_field(Attribute)
  """All attributes attached to the type."""
  documentation: list[Documentation] = ir_data_fields.list_field(Documentation)
  """Docs for the type."""
  # pylint:disable=undefined-variable
  subtype: list["TypeDefinition"] = ir_data_fields.list_field(
      lambda: TypeDefinition
  )
  """Subtypes of this type."""
  addressable_unit: Optional[AddressableUnit] = None

  runtime_parameter: list[RuntimeParameter] = ir_data_fields.list_field(
      RuntimeParameter
  )
  """If the type requires parameters at runtime, these are its parameters.

  These are currently only allowed on structures, but in the future they
  should be allowed on externals.
  """
  source_location: Optional[Location] = None


@dataclasses.dataclass
class Module(Message):
  """The IR for an individual Emboss module (file)."""

  attribute: list[Attribute] = ir_data_fields.list_field(Attribute)
  """Module-level attributes."""
  type: list[TypeDefinition] = ir_data_fields.list_field(TypeDefinition)
  """Module-level type definitions."""
  documentation: list[Documentation] = ir_data_fields.list_field(Documentation)
  """Module-level docs."""
  foreign_import: list[Import] = ir_data_fields.list_field(Import)
  """Other modules imported."""
  source_text: Optional[str] = None
  """The original source code."""
  source_location: Optional[Location] = None
  """Source code covered by this IR."""
  source_file_name: Optional[str] = None
  """Name of the source file."""


@dataclasses.dataclass
class EmbossIr(Message):
  """The top-level IR for an Emboss module and all of its dependencies."""

  module: list[Module] = ir_data_fields.list_field(Module)
  """All modules.

  The first entry will be the main module; back ends should
  generate code corresponding to that module.  The second entry will be the
  prelude module.
  """


# Post-process the dataclasses to add cached fields.
ir_data_fields.cache_message_specs(sys.modules[Message.__module__], Message)
