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

"""Tests for util.ir_util."""

import unittest
from compiler.util import expression_parser
from compiler.util import ir_data
from compiler.util import ir_data_utils
from compiler.util import ir_util


def _parse_expression(text):
  return expression_parser.parse(text)


class IrUtilTest(unittest.TestCase):
  """Tests for the miscellaneous utility functions in ir_util.py."""

  def test_is_constant_integer(self):
    self.assertTrue(ir_util.is_constant(_parse_expression("6")))
    expression = _parse_expression("12")
    # The type information should be ignored for constants like this one.
    ir_data_utils.builder(expression).type.integer.CopyFrom(ir_data.IntegerType())
    self.assertTrue(ir_util.is_constant(expression))

  def test_is_constant_boolean(self):
    self.assertTrue(ir_util.is_constant(_parse_expression("true")))
    expression = _parse_expression("true")
    # The type information should be ignored for constants like this one.
    ir_data_utils.builder(expression).type.boolean.CopyFrom(ir_data.BooleanType())
    self.assertTrue(ir_util.is_constant(expression))

  def test_is_constant_enum(self):
    self.assertTrue(ir_util.is_constant(ir_data.Expression(
        constant_reference=ir_data.Reference(),
        type=ir_data.ExpressionType(enumeration=ir_data.EnumType(value="12")))))

  def test_is_constant_integer_type(self):
    self.assertFalse(ir_util.is_constant_type(ir_data.ExpressionType(
        integer=ir_data.IntegerType(
            modulus="10",
            modular_value="5",
            minimum_value="-5",
            maximum_value="15"))))
    self.assertTrue(ir_util.is_constant_type(ir_data.ExpressionType(
        integer=ir_data.IntegerType(
            modulus="infinity",
            modular_value="5",
            minimum_value="5",
            maximum_value="5"))))

  def test_is_constant_boolean_type(self):
    self.assertFalse(ir_util.is_constant_type(ir_data.ExpressionType(
        boolean=ir_data.BooleanType())))
    self.assertTrue(ir_util.is_constant_type(ir_data.ExpressionType(
        boolean=ir_data.BooleanType(value=True))))
    self.assertTrue(ir_util.is_constant_type(ir_data.ExpressionType(
        boolean=ir_data.BooleanType(value=False))))

  def test_is_constant_enumeration_type(self):
    self.assertFalse(ir_util.is_constant_type(ir_data.ExpressionType(
        enumeration=ir_data.EnumType())))
    self.assertTrue(ir_util.is_constant_type(ir_data.ExpressionType(
        enumeration=ir_data.EnumType(value="0"))))

  def test_is_constant_opaque_type(self):
    self.assertFalse(ir_util.is_constant_type(ir_data.ExpressionType(
        opaque=ir_data.OpaqueType())))

  def test_constant_value_of_integer(self):
    self.assertEqual(6, ir_util.constant_value(_parse_expression("6")))

  def test_constant_value_of_none(self):
    self.assertIsNone(ir_util.constant_value(ir_data.Expression()))

  def test_constant_value_of_addition(self):
    self.assertEqual(6, ir_util.constant_value(_parse_expression("2+4")))

  def test_constant_value_of_subtraction(self):
    self.assertEqual(-2, ir_util.constant_value(_parse_expression("2-4")))

  def test_constant_value_of_multiplication(self):
    self.assertEqual(8, ir_util.constant_value(_parse_expression("2*4")))

  def test_constant_value_of_equality(self):
    self.assertFalse(ir_util.constant_value(_parse_expression("2 == 4")))

  def test_constant_value_of_inequality(self):
    self.assertTrue(ir_util.constant_value(_parse_expression("2 != 4")))

  def test_constant_value_of_less(self):
    self.assertTrue(ir_util.constant_value(_parse_expression("2 < 4")))

  def test_constant_value_of_less_or_equal(self):
    self.assertTrue(ir_util.constant_value(_parse_expression("2 <= 4")))

  def test_constant_value_of_greater(self):
    self.assertFalse(ir_util.constant_value(_parse_expression("2 > 4")))

  def test_constant_value_of_greater_or_equal(self):
    self.assertFalse(ir_util.constant_value(_parse_expression("2 >= 4")))

  def test_constant_value_of_and(self):
    self.assertFalse(ir_util.constant_value(_parse_expression("true && false")))
    self.assertTrue(ir_util.constant_value(_parse_expression("true && true")))

  def test_constant_value_of_or(self):
    self.assertTrue(ir_util.constant_value(_parse_expression("true || false")))
    self.assertFalse(
        ir_util.constant_value(_parse_expression("false || false")))

  def test_constant_value_of_choice(self):
    self.assertEqual(
        10, ir_util.constant_value(_parse_expression("false ? 20 : 10")))
    self.assertEqual(
        20, ir_util.constant_value(_parse_expression("true ? 20 : 10")))

  def test_constant_value_of_choice_with_unknown_other_branch(self):
    self.assertEqual(
        10, ir_util.constant_value(_parse_expression("false ? foo : 10")))
    self.assertEqual(
        20, ir_util.constant_value(_parse_expression("true ? 20 : foo")))

  def test_constant_value_of_maximum(self):
    self.assertEqual(10,
                     ir_util.constant_value(_parse_expression("$max(5, 10)")))
    self.assertEqual(10,
                     ir_util.constant_value(_parse_expression("$max(10)")))
    self.assertEqual(
        10,
        ir_util.constant_value(_parse_expression("$max(5, 9, 7, 10, 6, -100)")))

  def test_constant_value_of_boolean(self):
    self.assertTrue(ir_util.constant_value(_parse_expression("true")))
    self.assertFalse(ir_util.constant_value(_parse_expression("false")))

  def test_constant_value_of_enum(self):
    self.assertEqual(12, ir_util.constant_value(ir_data.Expression(
        constant_reference=ir_data.Reference(),
        type=ir_data.ExpressionType(enumeration=ir_data.EnumType(value="12")))))

  def test_constant_value_of_integer_reference(self):
    self.assertEqual(12, ir_util.constant_value(ir_data.Expression(
        constant_reference=ir_data.Reference(),
        type=ir_data.ExpressionType(
            integer=ir_data.IntegerType(modulus="infinity",
                                       modular_value="12")))))

  def test_constant_value_of_boolean_reference(self):
    self.assertTrue(ir_util.constant_value(ir_data.Expression(
        constant_reference=ir_data.Reference(),
        type=ir_data.ExpressionType(boolean=ir_data.BooleanType(value=True)))))

  def test_constant_value_of_builtin_reference(self):
    self.assertEqual(12, ir_util.constant_value(
        ir_data.Expression(
            builtin_reference=ir_data.Reference(
                canonical_name=ir_data.CanonicalName(object_path=["$foo"]))),
        {"$foo": 12}))

  def test_constant_value_of_field_reference(self):
    self.assertIsNone(ir_util.constant_value(_parse_expression("foo")))

  def test_constant_value_of_missing_builtin_reference(self):
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("$static_size_in_bits"), {"$bar": 12}))

  def test_constant_value_of_present_builtin_reference(self):
    self.assertEqual(12, ir_util.constant_value(
        _parse_expression("$static_size_in_bits"),
        {"$static_size_in_bits": 12}))

  def test_constant_false_value_of_operator_and_with_missing_value(self):
    self.assertIs(False, ir_util.constant_value(
        _parse_expression("false && foo"), {"bar": 12}))
    self.assertIs(False, ir_util.constant_value(
        _parse_expression("foo && false"), {"bar": 12}))

  def test_constant_false_value_of_operator_and_known_value(self):
    self.assertTrue(ir_util.constant_value(
        _parse_expression("true && $is_statically_sized"),
        {"$is_statically_sized": True}))

  def test_constant_none_value_of_operator_and_with_missing_value(self):
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("true && foo"), {"bar": 12}))
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("foo && true"), {"bar": 12}))

  def test_constant_false_value_of_operator_or_with_missing_value(self):
    self.assertTrue(ir_util.constant_value(
        _parse_expression("true || foo"), {"bar": 12}))
    self.assertTrue(ir_util.constant_value(
        _parse_expression("foo || true"), {"bar": 12}))

  def test_constant_none_value_of_operator_or_with_missing_value(self):
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("foo || false"), {"bar": 12}))
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("false || foo"), {"bar": 12}))

  def test_constant_value_of_operator_plus_with_missing_value(self):
    self.assertIsNone(ir_util.constant_value(
        _parse_expression("12 + foo"), {"bar": 12}))

  def test_is_array(self):
    self.assertTrue(
        ir_util.is_array(ir_data.Type(array_type=ir_data.ArrayType())))
    self.assertFalse(
        ir_util.is_array(ir_data.Type(atomic_type=ir_data.AtomicType())))

  def test_get_attribute(self):
    type_def = ir_data.TypeDefinition(attribute=[
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=ir_data.Expression()),
            name=ir_data.Word(text="phil")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("true")),
            name=ir_data.Word(text="bob")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob2")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("true")),
            name=ir_data.Word(text="bob2"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob3"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word()),
    ])
    self.assertEqual(
        ir_data.AttributeValue(expression=_parse_expression("true")),
        ir_util.get_attribute(type_def.attribute, "bob"))
    self.assertEqual(
        ir_data.AttributeValue(expression=_parse_expression("false")),
        ir_util.get_attribute(type_def.attribute, "bob2"))
    self.assertEqual(None, ir_util.get_attribute(type_def.attribute, "Bob"))
    self.assertEqual(None, ir_util.get_attribute(type_def.attribute, "bob3"))

  def test_get_boolean_attribute(self):
    type_def = ir_data.TypeDefinition(attribute=[
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=ir_data.Expression()),
            name=ir_data.Word(text="phil")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("true")),
            name=ir_data.Word(text="bob")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob2")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("true")),
            name=ir_data.Word(text="bob2"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob3"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word()),
    ])
    self.assertTrue(ir_util.get_boolean_attribute(type_def.attribute, "bob"))
    self.assertTrue(ir_util.get_boolean_attribute(type_def.attribute,
                                                  "bob",
                                                  default_value=False))
    self.assertFalse(ir_util.get_boolean_attribute(type_def.attribute, "bob2"))
    self.assertFalse(ir_util.get_boolean_attribute(type_def.attribute,
                                                   "bob2",
                                                   default_value=True))
    self.assertIsNone(ir_util.get_boolean_attribute(type_def.attribute, "Bob"))
    self.assertTrue(ir_util.get_boolean_attribute(type_def.attribute,
                                                  "Bob",
                                                  default_value=True))
    self.assertIsNone(ir_util.get_boolean_attribute(type_def.attribute, "bob3"))

  def test_get_integer_attribute(self):
    type_def = ir_data.TypeDefinition(attribute=[
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType()))),
            name=ir_data.Word(text="phil")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="20"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="20",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="10"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="10",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="5"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="5",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob2")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="0"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="0",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob2"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="30"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="30",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob3"),
            is_default=True),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    function=ir_data.Function(
                        function=ir_data.FunctionMapping.ADDITION,
                        args=[
                            ir_data.Expression(
                                constant=ir_data.NumericConstant(value="100"),
                                type=ir_data.ExpressionType(
                                    integer=ir_data.IntegerType(
                                        modular_value="100",
                                        modulus="infinity"))),
                            ir_data.Expression(
                                constant=ir_data.NumericConstant(value="100"),
                                type=ir_data.ExpressionType(
                                    integer=ir_data.IntegerType(
                                        modular_value="100",
                                        modulus="infinity")))
                        ]),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="200",
                        modulus="infinity")))),
            name=ir_data.Word(text="bob4")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(
                expression=ir_data.Expression(
                    constant=ir_data.NumericConstant(value="40"),
                    type=ir_data.ExpressionType(integer=ir_data.IntegerType(
                        modular_value="40",
                        modulus="infinity")))),
            name=ir_data.Word()),
    ])
    self.assertEqual(10,
                     ir_util.get_integer_attribute(type_def.attribute, "bob"))
    self.assertEqual(5,
                     ir_util.get_integer_attribute(type_def.attribute, "bob2"))
    self.assertIsNone(ir_util.get_integer_attribute(type_def.attribute, "Bob"))
    self.assertEqual(10, ir_util.get_integer_attribute(type_def.attribute,
                                                       "Bob",
                                                       default_value=10))
    self.assertIsNone(ir_util.get_integer_attribute(type_def.attribute, "bob3"))
    self.assertEqual(200, ir_util.get_integer_attribute(type_def.attribute,
                                                        "bob4"))

  def test_get_duplicate_attribute(self):
    type_def = ir_data.TypeDefinition(attribute=[
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=ir_data.Expression()),
            name=ir_data.Word(text="phil")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("true")),
            name=ir_data.Word(text="bob")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word(text="bob")),
        ir_data.Attribute(
            value=ir_data.AttributeValue(expression=_parse_expression("false")),
            name=ir_data.Word()),
    ])
    self.assertRaises(AssertionError, ir_util.get_attribute, type_def.attribute,
                      "bob")

  def test_find_object(self):
    ir = ir_data_utils.IrDataSerializer.from_json(ir_data.EmbossIr,
        """{
          "module": [
            {
              "type": [
                {
                  "structure": {
                    "field": [
                      {
                        "name": {
                          "name": { "text": "field" },
                          "canonical_name": {
                            "module_file": "test.emb",
                            "object_path": [ "Foo", "field" ]
                          }
                        }
                      }
                    ]
                  },
                  "name": {
                    "name": { "text": "Foo" },
                    "canonical_name": {
                      "module_file": "test.emb",
                      "object_path": [ "Foo" ]
                    }
                  },
                  "runtime_parameter": [
                    {
                      "name": {
                        "name": { "text": "parameter" },
                        "canonical_name": {
                          "module_file": "test.emb",
                          "object_path": [ "Foo", "parameter" ]
                        }
                      }
                    }
                  ]
                },
                {
                  "enumeration": {
                    "value": [
                      {
                        "name": {
                          "name": { "text": "QUX" },
                          "canonical_name": {
                            "module_file": "test.emb",
                            "object_path": [ "Bar", "QUX" ]
                          }
                        }
                      }
                    ]
                  },
                  "name": {
                    "name": { "text": "Bar" },
                    "canonical_name": {
                      "module_file": "test.emb",
                      "object_path": [ "Bar" ]
                    }
                  }
                }
              ],
              "source_file_name": "test.emb"
            },
            {
              "type": [
                {
                  "external": {},
                  "name": {
                    "name": { "text": "UInt" },
                    "canonical_name": {
                      "module_file": "",
                      "object_path": [ "UInt" ]
                    }
                  }
                }
              ],
              "source_file_name": ""
            }
          ]
        }""")

    # Test that find_object works with any of its four "name" types.
    canonical_name_of_foo = ir_data.CanonicalName(module_file="test.emb",
                                                 object_path=["Foo"])
    self.assertEqual(ir.module[0].type[0], ir_util.find_object(
        ir_data.Reference(canonical_name=canonical_name_of_foo), ir))
    self.assertEqual(ir.module[0].type[0], ir_util.find_object(
        ir_data.NameDefinition(canonical_name=canonical_name_of_foo), ir))
    self.assertEqual(ir.module[0].type[0],
                     ir_util.find_object(canonical_name_of_foo, ir))
    self.assertEqual(ir.module[0].type[0],
                     ir_util.find_object(("test.emb", "Foo"), ir))

    # Test that find_object works with objects other than structs.
    self.assertEqual(ir.module[0].type[1],
                     ir_util.find_object(("test.emb", "Bar"), ir))
    self.assertEqual(ir.module[1].type[0],
                     ir_util.find_object(("", "UInt"), ir))
    self.assertEqual(ir.module[0].type[0].structure.field[0],
                     ir_util.find_object(("test.emb", "Foo", "field"), ir))
    self.assertEqual(ir.module[0].type[0].runtime_parameter[0],
                     ir_util.find_object(("test.emb", "Foo", "parameter"), ir))
    self.assertEqual(ir.module[0].type[1].enumeration.value[0],
                     ir_util.find_object(("test.emb", "Bar", "QUX"), ir))
    self.assertEqual(ir.module[0], ir_util.find_object(("test.emb",), ir))
    self.assertEqual(ir.module[1], ir_util.find_object(("",), ir))

    # Test searching for non-present objects.
    self.assertIsNone(ir_util.find_object_or_none(("not_test.emb",), ir))
    self.assertIsNone(ir_util.find_object_or_none(("test.emb", "NotFoo"), ir))
    self.assertIsNone(
        ir_util.find_object_or_none(("test.emb", "Foo", "not_field"), ir))
    self.assertIsNone(
        ir_util.find_object_or_none(("test.emb", "Foo", "field", "no_subfield"),
                                    ir))
    self.assertIsNone(
        ir_util.find_object_or_none(("test.emb", "Bar", "NOT_QUX"), ir))
    self.assertIsNone(
        ir_util.find_object_or_none(("test.emb", "Bar", "QUX", "no_subenum"),
                                    ir))

    # Test that find_parent_object works with any of its four "name" types.
    self.assertEqual(ir.module[0], ir_util.find_parent_object(
        ir_data.Reference(canonical_name=canonical_name_of_foo), ir))
    self.assertEqual(ir.module[0], ir_util.find_parent_object(
        ir_data.NameDefinition(canonical_name=canonical_name_of_foo), ir))
    self.assertEqual(ir.module[0],
                     ir_util.find_parent_object(canonical_name_of_foo, ir))
    self.assertEqual(ir.module[0],
                     ir_util.find_parent_object(("test.emb", "Foo"), ir))

    # Test that find_parent_object works with objects other than structs.
    self.assertEqual(ir.module[0],
                     ir_util.find_parent_object(("test.emb", "Bar"), ir))
    self.assertEqual(ir.module[1], ir_util.find_parent_object(("", "UInt"), ir))
    self.assertEqual(ir.module[0].type[0],
                     ir_util.find_parent_object(("test.emb", "Foo", "field"),
                                                ir))
    self.assertEqual(ir.module[0].type[1],
                     ir_util.find_parent_object(("test.emb", "Bar", "QUX"), ir))

  def test_hashable_form_of_reference(self):
    self.assertEqual(
        ("t.emb", "Foo", "Bar"),
        ir_util.hashable_form_of_reference(ir_data.Reference(
            canonical_name=ir_data.CanonicalName(module_file="t.emb",
                                                object_path=["Foo", "Bar"]))))
    self.assertEqual(
        ("t.emb", "Foo", "Bar"),
        ir_util.hashable_form_of_reference(ir_data.NameDefinition(
            canonical_name=ir_data.CanonicalName(module_file="t.emb",
                                                object_path=["Foo", "Bar"]))))

  def test_get_base_type(self):
    array_type_ir = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "array_type": {
            "element_count": { "constant": { "value": "20" } },
            "base_type": {
              "array_type": {
                "element_count": { "constant": { "value": "10" } },
                "base_type": {
                  "atomic_type": {
                    "reference": { },
                    "source_location": { "start": { "line": 5 } }
                  }
                },
                "source_location": { "start": { "line": 4 } }
              }
            },
            "source_location": { "start": { "line": 3 } }
          }
        }""")
    base_type_ir = array_type_ir.array_type.base_type.array_type.base_type
    self.assertEqual(base_type_ir, ir_util.get_base_type(array_type_ir))
    self.assertEqual(base_type_ir, ir_util.get_base_type(
        array_type_ir.array_type.base_type))
    self.assertEqual(base_type_ir, ir_util.get_base_type(base_type_ir))

  def test_size_of_type_in_bits(self):
    ir = ir_data_utils.IrDataSerializer.from_json(ir_data.EmbossIr,
        """{
          "module": [{
            "type": [{
              "name": {
                "name": { "text": "Baz" },
                "canonical_name": {
                  "module_file": "s.emb",
                  "object_path": ["Baz"]
                }
              }
            }],
            "source_file_name": "s.emb"
          },
          {
            "type": [{
              "name": {
                "name": { "text": "UInt" },
                "canonical_name": {
                  "module_file": "",
                  "object_path": ["UInt"]
                }
              }
            },
            {
              "name": {
                "name": { "text": "Byte" },
                "canonical_name": {
                  "module_file": "",
                  "object_path": ["Byte"]
                }
              },
              "attribute": [{
                "name": { "text": "fixed_size_in_bits" },
                "value": {
                  "expression": {
                    "constant": { "value": "8" },
                    "type": {
                      "integer": { "modular_value": "8", "modulus": "infinity" }
                    }
                  }
                }
              }]
            }],
            "source_file_name": ""
          }]
        }""")

    fixed_size_type = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "atomic_type": {
            "reference": {
              "canonical_name": { "module_file": "", "object_path": ["Byte"] }
             }
          }
        }""")
    self.assertEqual(8, ir_util.fixed_size_of_type_in_bits(fixed_size_type, ir))

    explicit_size_type = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "atomic_type": {
            "reference": {
              "canonical_name": { "module_file": "", "object_path": ["UInt"] }
            }
          },
          "size_in_bits": {
            "constant": { "value": "32" },
            "type": {
              "integer": { "modular_value": "32", "modulus": "infinity" }
            }
          }
        }""")
    self.assertEqual(32,
                     ir_util.fixed_size_of_type_in_bits(explicit_size_type, ir))

    fixed_size_array = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "array_type": {
            "base_type": {
              "atomic_type": {
                "reference": {
                  "canonical_name": { "module_file": "", "object_path": ["Byte"] }
                }
              }
            },
            "element_count": {
              "constant": { "value": "5" },
              "type": {
                "integer": { "modular_value": "5", "modulus": "infinity" }
              }
            }
          }
        }""")
    self.assertEqual(40,
                     ir_util.fixed_size_of_type_in_bits(fixed_size_array, ir))

    fixed_size_2d_array = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "array_type": {
            "base_type": {
              "array_type": {
                "base_type": {
                  "atomic_type": {
                    "reference": {
                      "canonical_name": {
                        "module_file": "",
                        "object_path": ["Byte"]
                      }
                    }
                  }
                },
                "element_count": {
                  "constant": { "value": "5" },
                  "type": {
                    "integer": { "modular_value": "5", "modulus": "infinity" }
                  }
                }
              }
            },
            "element_count": {
              "constant": { "value": "2" },
              "type": {
                "integer": { "modular_value": "2", "modulus": "infinity" }
              }
            }
          }
        }""")
    self.assertEqual(
        80, ir_util.fixed_size_of_type_in_bits(fixed_size_2d_array, ir))

    automatic_size_array = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "array_type": {
            "base_type": {
              "array_type": {
                "base_type": {
                  "atomic_type": {
                    "reference": {
                      "canonical_name": {
                        "module_file": "",
                        "object_path": ["Byte"]
                      }
                    }
                  }
                },
                "element_count": {
                  "constant": { "value": "5" },
                  "type": {
                    "integer": { "modular_value": "5", "modulus": "infinity" }
                  }
                }
              }
            },
            "automatic": { }
          }
      }""")
    self.assertIsNone(
        ir_util.fixed_size_of_type_in_bits(automatic_size_array, ir))

    variable_size_type = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "atomic_type": {
            "reference": {
              "canonical_name": { "module_file": "", "object_path": ["UInt"] }
            }
          }
        }""")
    self.assertIsNone(
        ir_util.fixed_size_of_type_in_bits(variable_size_type, ir))

    no_size_type = ir_data_utils.IrDataSerializer.from_json(ir_data.Type,
        """{
          "atomic_type": {
            "reference": {
              "canonical_name": {
                "module_file": "s.emb",
                "object_path": ["Baz"]
              }
            }
          }
        }""")
    self.assertIsNone(ir_util.fixed_size_of_type_in_bits(no_size_type, ir))

  def test_field_is_virtual(self):
    self.assertTrue(ir_util.field_is_virtual(ir_data.Field()))

  def test_field_is_not_virtual(self):
    self.assertFalse(ir_util.field_is_virtual(
        ir_data.Field(location=ir_data.FieldLocation())))

  def test_field_is_read_only(self):
    self.assertTrue(ir_util.field_is_read_only(ir_data.Field(
        write_method=ir_data.WriteMethod(read_only=True))))

  def test_field_is_not_read_only(self):
    self.assertFalse(ir_util.field_is_read_only(
        ir_data.Field(location=ir_data.FieldLocation())))
    self.assertFalse(ir_util.field_is_read_only(ir_data.Field(
        write_method=ir_data.WriteMethod())))


if __name__ == "__main__":
  unittest.main()
