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

import unittest
from compiler.front_end import expression_bounds
from compiler.front_end import glue
from compiler.util import test_util


class ComputeConstantsTest(unittest.TestCase):

  def _make_ir(self, emb_text):
    ir, unused_debug_info, errors = glue.parse_emboss_file(
        "m.emb",
        test_util.dict_file_reader({"m.emb": emb_text}),
        stop_before_step="compute_constants")
    assert not errors, errors
    return ir

  def test_constant_integer(self):
    ir = self._make_ir("struct Foo:\n"
                       "  10 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    start = ir.module[0].type[0].structure.field[0].location.start
    self.assertEqual("10", start.type.integer.minimum_value)
    self.assertEqual("10", start.type.integer.maximum_value)
    self.assertEqual("10", start.type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)

  def test_boolean_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if true:\n"
                       "    0 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expression = ir.module[0].type[0].structure.field[0].existence_condition
    self.assertTrue(expression.type.boolean.HasField("value"))
    self.assertTrue(expression.type.boolean.value)

  def test_constant_equality(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 == 5:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 == 6:\n"
                       "    0 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    true_condition = structure.field[0].existence_condition
    false_condition = structure.field[1].existence_condition
    self.assertTrue(true_condition.type.boolean.HasField("value"))
    self.assertTrue(true_condition.type.boolean.value)
    self.assertTrue(false_condition.type.boolean.HasField("value"))
    self.assertFalse(false_condition.type.boolean.value)

  def test_constant_inequality(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 != 5:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 != 6:\n"
                       "    0 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    false_condition = structure.field[0].existence_condition
    true_condition = structure.field[1].existence_condition
    self.assertTrue(false_condition.type.boolean.HasField("value"))
    self.assertFalse(false_condition.type.boolean.value)
    self.assertTrue(true_condition.type.boolean.HasField("value"))
    self.assertTrue(true_condition.type.boolean.value)

  def test_constant_less_than(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 < 4:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 < 5:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if 5 < 6:\n"
                       "    0 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    greater_than_condition = structure.field[0].existence_condition
    equal_condition = structure.field[1].existence_condition
    less_than_condition = structure.field[2].existence_condition
    self.assertTrue(greater_than_condition.type.boolean.HasField("value"))
    self.assertFalse(greater_than_condition.type.boolean.value)
    self.assertTrue(equal_condition.type.boolean.HasField("value"))
    self.assertFalse(equal_condition.type.boolean.value)
    self.assertTrue(less_than_condition.type.boolean.HasField("value"))
    self.assertTrue(less_than_condition.type.boolean.value)

  def test_constant_less_than_or_equal(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 <= 4:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 <= 5:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if 5 <= 6:\n"
                       "    0 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    greater_than_condition = structure.field[0].existence_condition
    equal_condition = structure.field[1].existence_condition
    less_than_condition = structure.field[2].existence_condition
    self.assertTrue(greater_than_condition.type.boolean.HasField("value"))
    self.assertFalse(greater_than_condition.type.boolean.value)
    self.assertTrue(equal_condition.type.boolean.HasField("value"))
    self.assertTrue(equal_condition.type.boolean.value)
    self.assertTrue(less_than_condition.type.boolean.HasField("value"))
    self.assertTrue(less_than_condition.type.boolean.value)

  def test_constant_greater_than(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 > 4:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 > 5:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if 5 > 6:\n"
                       "    0 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    greater_than_condition = structure.field[0].existence_condition
    equal_condition = structure.field[1].existence_condition
    less_than_condition = structure.field[2].existence_condition
    self.assertTrue(greater_than_condition.type.boolean.HasField("value"))
    self.assertTrue(greater_than_condition.type.boolean.value)
    self.assertTrue(equal_condition.type.boolean.HasField("value"))
    self.assertFalse(equal_condition.type.boolean.value)
    self.assertTrue(less_than_condition.type.boolean.HasField("value"))
    self.assertFalse(less_than_condition.type.boolean.value)

  def test_constant_greater_than_or_equal(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 >= 4:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if 5 >= 5:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if 5 >= 6:\n"
                       "    0 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    greater_than_condition = structure.field[0].existence_condition
    equal_condition = structure.field[1].existence_condition
    less_than_condition = structure.field[2].existence_condition
    self.assertTrue(greater_than_condition.type.boolean.HasField("value"))
    self.assertTrue(greater_than_condition.type.boolean.value)
    self.assertTrue(equal_condition.type.boolean.HasField("value"))
    self.assertTrue(equal_condition.type.boolean.value)
    self.assertTrue(less_than_condition.type.boolean.HasField("value"))
    self.assertFalse(less_than_condition.type.boolean.value)

  def test_constant_and(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if false && false:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if true && false:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if false && true:\n"
                       "    0 [+1]  UInt  z\n"
                       "  if true && true:\n"
                       "    0 [+1]  UInt  w\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    false_false_condition = structure.field[0].existence_condition
    true_false_condition = structure.field[1].existence_condition
    false_true_condition = structure.field[2].existence_condition
    true_true_condition = structure.field[3].existence_condition
    self.assertTrue(false_false_condition.type.boolean.HasField("value"))
    self.assertFalse(false_false_condition.type.boolean.value)
    self.assertTrue(true_false_condition.type.boolean.HasField("value"))
    self.assertFalse(true_false_condition.type.boolean.value)
    self.assertTrue(false_true_condition.type.boolean.HasField("value"))
    self.assertFalse(false_true_condition.type.boolean.value)
    self.assertTrue(true_true_condition.type.boolean.HasField("value"))
    self.assertTrue(true_true_condition.type.boolean.value)

  def test_constant_or(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if false || false:\n"
                       "    0 [+1]  UInt  x\n"
                       "  if true || false:\n"
                       "    0 [+1]  UInt  y\n"
                       "  if false || true:\n"
                       "    0 [+1]  UInt  z\n"
                       "  if true || true:\n"
                       "    0 [+1]  UInt  w\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    false_false_condition = structure.field[0].existence_condition
    true_false_condition = structure.field[1].existence_condition
    false_true_condition = structure.field[2].existence_condition
    true_true_condition = structure.field[3].existence_condition
    self.assertTrue(false_false_condition.type.boolean.HasField("value"))
    self.assertFalse(false_false_condition.type.boolean.value)
    self.assertTrue(true_false_condition.type.boolean.HasField("value"))
    self.assertTrue(true_false_condition.type.boolean.value)
    self.assertTrue(false_true_condition.type.boolean.HasField("value"))
    self.assertTrue(false_true_condition.type.boolean.value)
    self.assertTrue(true_true_condition.type.boolean.HasField("value"))
    self.assertTrue(true_true_condition.type.boolean.value)

  def test_enum_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if Bar.QUX == Bar.QUX:\n"
                       "    0 [+1]  Bar  x\n"
                       "enum Bar:\n"
                       "  QUX = 12\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    condition = ir.module[0].type[0].structure.field[0].existence_condition
    left = condition.function.args[0]
    self.assertEqual("12", left.type.enumeration.value)

  def test_non_constant_field_reference(self):
    ir = self._make_ir("struct Foo:\n"
                       "  y [+1]  UInt  x\n"
                       "  0 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    start = ir.module[0].type[0].structure.field[0].location.start
    self.assertEqual("0", start.type.integer.minimum_value)
    self.assertEqual("255", start.type.integer.maximum_value)
    self.assertEqual("0", start.type.integer.modular_value)
    self.assertEqual("1", start.type.integer.modulus)

  def test_field_reference_bounds_are_uncomputable(self):
    # Variable-sized UInt/Int/Bcd should not cause an error here: they are
    # handled in the constraints pass.
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  UInt  x\n"
                       "  0 [+x]  UInt  y\n"
                       "  y [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))

  def test_field_references_references_bounds_are_uncomputable(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  UInt  x\n"
                       "  0 [+x]  UInt  y\n"
                       "  0 [+y]  UInt  z\n"
                       "  z [+1]  UInt  q\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))

  def test_non_constant_equality(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 5 == y:\n"
                       "    0 [+1]  UInt  x\n"
                       "  0 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    structure = ir.module[0].type[0].structure
    condition = structure.field[0].existence_condition
    self.assertFalse(condition.type.boolean.HasField("value"))

  def test_constant_addition(self):
    ir = self._make_ir("struct Foo:\n"
                       "  7+5 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    start = ir.module[0].type[0].structure.field[0].location.start
    self.assertEqual("12", start.type.integer.minimum_value)
    self.assertEqual("12", start.type.integer.maximum_value)
    self.assertEqual("12", start.type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("7", start.function.args[0].type.integer.minimum_value)
    self.assertEqual("7", start.function.args[0].type.integer.maximum_value)
    self.assertEqual("7", start.function.args[0].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("5", start.function.args[1].type.integer.minimum_value)
    self.assertEqual("5", start.function.args[1].type.integer.maximum_value)
    self.assertEqual("5", start.function.args[1].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)

  def test_constant_subtraction(self):
    ir = self._make_ir("struct Foo:\n"
                       "  7-5 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    start = ir.module[0].type[0].structure.field[0].location.start
    self.assertEqual("2", start.type.integer.minimum_value)
    self.assertEqual("2", start.type.integer.maximum_value)
    self.assertEqual("2", start.type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("7", start.function.args[0].type.integer.minimum_value)
    self.assertEqual("7", start.function.args[0].type.integer.maximum_value)
    self.assertEqual("7", start.function.args[0].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("5", start.function.args[1].type.integer.minimum_value)
    self.assertEqual("5", start.function.args[1].type.integer.maximum_value)
    self.assertEqual("5", start.function.args[1].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)

  def test_constant_multiplication(self):
    ir = self._make_ir("struct Foo:\n"
                       "  7*5 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    start = ir.module[0].type[0].structure.field[0].location.start
    self.assertEqual("35", start.type.integer.minimum_value)
    self.assertEqual("35", start.type.integer.maximum_value)
    self.assertEqual("35", start.type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("7", start.function.args[0].type.integer.minimum_value)
    self.assertEqual("7", start.function.args[0].type.integer.maximum_value)
    self.assertEqual("7", start.function.args[0].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)
    self.assertEqual("5", start.function.args[1].type.integer.minimum_value)
    self.assertEqual("5", start.function.args[1].type.integer.maximum_value)
    self.assertEqual("5", start.function.args[1].type.integer.modular_value)
    self.assertEqual("infinity", start.type.integer.modulus)

  def test_nested_constant_expression(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if 7*(3+1) == 28:\n"
                       "    0 [+1]  UInt  x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    condition = ir.module[0].type[0].structure.field[0].existence_condition
    self.assertTrue(condition.type.boolean.value)
    condition_left = condition.function.args[0]
    self.assertEqual("28", condition_left.type.integer.minimum_value)
    self.assertEqual("28", condition_left.type.integer.maximum_value)
    self.assertEqual("28", condition_left.type.integer.modular_value)
    self.assertEqual("infinity", condition_left.type.integer.modulus)
    condition_left_left = condition_left.function.args[0]
    self.assertEqual("7", condition_left_left.type.integer.minimum_value)
    self.assertEqual("7", condition_left_left.type.integer.maximum_value)
    self.assertEqual("7", condition_left_left.type.integer.modular_value)
    self.assertEqual("infinity", condition_left_left.type.integer.modulus)
    condition_left_right = condition_left.function.args[1]
    self.assertEqual("4", condition_left_right.type.integer.minimum_value)
    self.assertEqual("4", condition_left_right.type.integer.maximum_value)
    self.assertEqual("4", condition_left_right.type.integer.modular_value)
    self.assertEqual("infinity", condition_left_right.type.integer.modulus)
    condition_left_right_left = condition_left_right.function.args[0]
    self.assertEqual("3", condition_left_right_left.type.integer.minimum_value)
    self.assertEqual("3", condition_left_right_left.type.integer.maximum_value)
    self.assertEqual("3", condition_left_right_left.type.integer.modular_value)
    self.assertEqual("infinity", condition_left_right_left.type.integer.modulus)
    condition_left_right_right = condition_left_right.function.args[1]
    self.assertEqual("1", condition_left_right_right.type.integer.minimum_value)
    self.assertEqual("1", condition_left_right_right.type.integer.maximum_value)
    self.assertEqual("1", condition_left_right_right.type.integer.modular_value)
    self.assertEqual("infinity",
                     condition_left_right_right.type.integer.modulus)

  def test_constant_plus_non_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0       [+1]  UInt  x\n"
                       "  5+(4*x) [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual("4", y_start.type.integer.modulus)
    self.assertEqual("1", y_start.type.integer.modular_value)
    self.assertEqual("5", y_start.type.integer.minimum_value)
    self.assertEqual("1025", y_start.type.integer.maximum_value)

  def test_constant_minus_non_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0       [+1]  UInt  x\n"
                       "  5-(4*x) [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual("4", y_start.type.integer.modulus)
    self.assertEqual("1", y_start.type.integer.modular_value)
    self.assertEqual("-1015", y_start.type.integer.minimum_value)
    self.assertEqual("5", y_start.type.integer.maximum_value)

  def test_non_constant_minus_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0       [+1]  UInt  x\n"
                       "  (4*x)-5 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual(str((4 * 0) - 5), y_start.type.integer.minimum_value)
    self.assertEqual(str((4 * 255) - 5), y_start.type.integer.maximum_value)
    self.assertEqual("4", y_start.type.integer.modulus)
    self.assertEqual("3", y_start.type.integer.modular_value)

  def test_non_constant_plus_non_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0             [+1]  UInt  x\n"
                       "  1             [+1]  UInt  y\n"
                       "  (4*x)+(6*y+3) [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("3", z_start.type.integer.minimum_value)
    self.assertEqual(str(4 * 255 + 6 * 255 + 3),
                     z_start.type.integer.maximum_value)
    self.assertEqual("2", z_start.type.integer.modulus)
    self.assertEqual("1", z_start.type.integer.modular_value)

  def test_non_constant_minus_non_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0            [+1]  UInt  x\n"
                       "  1            [+1]  UInt  y\n"
                       "  (x*3)-(y*3)  [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("3", z_start.type.integer.modulus)
    self.assertEqual("0", z_start.type.integer.modular_value)
    self.assertEqual(str(-3 * 255), z_start.type.integer.minimum_value)
    self.assertEqual(str(3 * 255), z_start.type.integer.maximum_value)

  def test_non_constant_times_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0         [+1]  UInt  x\n"
                       "  (4*x+1)*5 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual("20", y_start.type.integer.modulus)
    self.assertEqual("5", y_start.type.integer.modular_value)
    self.assertEqual("5", y_start.type.integer.minimum_value)
    self.assertEqual(str((4 * 255 + 1) * 5), y_start.type.integer.maximum_value)

  def test_non_constant_times_negative_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0          [+1]  UInt  x\n"
                       "  (4*x+1)*-5 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual("20", y_start.type.integer.modulus)
    self.assertEqual("15", y_start.type.integer.modular_value)
    self.assertEqual(str((4 * 255 + 1) * -5),
                     y_start.type.integer.minimum_value)
    self.assertEqual("-5", y_start.type.integer.maximum_value)

  def test_non_constant_times_zero(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0         [+1]  UInt  x\n"
                       "  (4*x+1)*0 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    y_start = ir.module[0].type[0].structure.field[1].location.start
    self.assertEqual("infinity", y_start.type.integer.modulus)
    self.assertEqual("0", y_start.type.integer.modular_value)
    self.assertEqual("0", y_start.type.integer.minimum_value)
    self.assertEqual("0", y_start.type.integer.maximum_value)

  def test_non_constant_times_non_constant_shared_modulus(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0               [+1]  UInt  x\n"
                       "  1               [+1]  UInt  y\n"
                       "  (4*x+3)*(4*y+3) [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("4", z_start.type.integer.modulus)
    self.assertEqual("1", z_start.type.integer.modular_value)
    self.assertEqual("9", z_start.type.integer.minimum_value)
    self.assertEqual(str((4 * 255 + 3)**2), z_start.type.integer.maximum_value)

  def test_non_constant_times_non_constant_congruent_to_zero(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0           [+1]  UInt  x\n"
                       "  1           [+1]  UInt  y\n"
                       "  (4*x)*(4*y) [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("16", z_start.type.integer.modulus)
    self.assertEqual("0", z_start.type.integer.modular_value)
    self.assertEqual("0", z_start.type.integer.minimum_value)
    self.assertEqual(str((4 * 255)**2), z_start.type.integer.maximum_value)

  def test_non_constant_times_non_constant_partially_shared_modulus(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0               [+1]  UInt  x\n"
                       "  1               [+1]  UInt  y\n"
                       "  (4*x+3)*(8*y+3) [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("4", z_start.type.integer.modulus)
    self.assertEqual("1", z_start.type.integer.modular_value)
    self.assertEqual("9", z_start.type.integer.minimum_value)
    self.assertEqual(str((4 * 255 + 3) * (8 * 255 + 3)),
                     z_start.type.integer.maximum_value)

  def test_non_constant_times_non_constant_full_complexity(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0                  [+1]  UInt  x\n"
                       "  1                  [+1]  UInt  y\n"
                       "  (12*x+9)*(40*y+15) [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("60", z_start.type.integer.modulus)
    self.assertEqual("15", z_start.type.integer.modular_value)
    self.assertEqual(str(9 * 15), z_start.type.integer.minimum_value)
    self.assertEqual(str((12 * 255 + 9) * (40 * 255 + 15)),
                     z_start.type.integer.maximum_value)

  def test_signed_non_constant_times_signed_non_constant_full_complexity(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0                  [+1]  Int  x\n"
                       "  1                  [+1]  Int  y\n"
                       "  (12*x+9)*(40*y+15) [+1]  Int  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("60", z_start.type.integer.modulus)
    self.assertEqual("15", z_start.type.integer.modular_value)
    # Max x/min y is slightly lower than min x/max y (-7825965 vs -7780065).
    self.assertEqual(str((12 * 127 + 9) * (40 * -128 + 15)),
                     z_start.type.integer.minimum_value)
    # Max x/max y is slightly higher than min x/min y (7810635 vs 7795335).
    self.assertEqual(str((12 * 127 + 9) * (40 * 127 + 15)),
                     z_start.type.integer.maximum_value)

  def test_non_constant_times_non_constant_flipped_min_max(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0             [+1]  UInt  x\n"
                       "  1             [+1]  UInt  y\n"
                       "  (-x*3)*(y*3)  [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("9", z_start.type.integer.modulus)
    self.assertEqual("0", z_start.type.integer.modular_value)
    self.assertEqual(str(-((3 * 255)**2)), z_start.type.integer.minimum_value)
    self.assertEqual("0", z_start.type.integer.maximum_value)

  # Currently, only `$static_size_in_bits` has an infinite bound, so all of the
  # examples below use `$static_size_in_bits`.  Unfortunately, this also means
  # that these tests rely on the fact that Emboss doesn't try to do any term
  # rewriting or smart correlation between the arguments of various operators:
  # for example, several tests rely on `$static_size_in_bits -
  # $static_size_in_bits` having the range `-infinity` to `infinity`, when a
  # trivial term rewrite would turn that expression into `0`.
  #
  # Unbounded expressions are only allowed at compile-time anyway, so these
  # tests cover some fairly unlikely uses of the Emboss expression language.
  def test_unbounded_plus_constant(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: $static_size_in_bits + 2 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("2", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_negative_unbounded_plus_constant(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: -$static_size_in_bits + 2 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("2", expr.type.integer.maximum_value)

  def test_negative_unbounded_plus_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: -$static_size_in_bits + $static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_unbounded_minus_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: $static_size_in_bits - $static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_unbounded_minus_negative_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: $static_size_in_bits - -$static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("0", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_unbounded_times_constant(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: ($static_size_in_bits + 1) * 2 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("2", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("2", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_unbounded_times_negative_constant(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: ($static_size_in_bits + 1) * -2 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("2", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("-2", expr.type.integer.maximum_value)

  def test_unbounded_times_negative_zero(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: ($static_size_in_bits + 1) * 0 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("infinity", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("0", expr.type.integer.minimum_value)
    self.assertEqual("0", expr.type.integer.maximum_value)

  def test_negative_unbounded_times_constant(self):
    ir = self._make_ir("external Foo:\n"
                       "  [requires: (-$static_size_in_bits + 1) * 2 > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("2", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("2", expr.type.integer.maximum_value)

  def test_double_unbounded_minus_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: 2 * $static_size_in_bits - $static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_double_unbounded_times_negative_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: 2 * $static_size_in_bits * -$static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("2", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("0", expr.type.integer.maximum_value)

  def test_upper_bound_of_field(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  Int  x\n"
                       "  let u = $upper_bound(x)\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    u_type = ir.module[0].type[0].structure.field[1].read_transform.type
    self.assertEqual("infinity", u_type.integer.modulus)
    self.assertEqual("127", u_type.integer.maximum_value)
    self.assertEqual("127", u_type.integer.minimum_value)
    self.assertEqual("127", u_type.integer.modular_value)

  def test_lower_bound_of_field(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  Int  x\n"
                       "  let l = $lower_bound(x)\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    l_type = ir.module[0].type[0].structure.field[1].read_transform.type
    self.assertEqual("infinity", l_type.integer.modulus)
    self.assertEqual("-128", l_type.integer.maximum_value)
    self.assertEqual("-128", l_type.integer.minimum_value)
    self.assertEqual("-128", l_type.integer.modular_value)

  def test_upper_bound_of_max(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  Int   x\n"
                       "  1 [+1]  UInt  y\n"
                       "  let u = $upper_bound($max(x, y))\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    u_type = ir.module[0].type[0].structure.field[2].read_transform.type
    self.assertEqual("infinity", u_type.integer.modulus)
    self.assertEqual("255", u_type.integer.maximum_value)
    self.assertEqual("255", u_type.integer.minimum_value)
    self.assertEqual("255", u_type.integer.modular_value)

  def test_lower_bound_of_max(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  Int  x\n"
                       "  1 [+1]  UInt  y\n"
                       "  let l = $lower_bound($max(x, y))\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    l_type = ir.module[0].type[0].structure.field[2].read_transform.type
    self.assertEqual("infinity", l_type.integer.modulus)
    self.assertEqual("0", l_type.integer.maximum_value)
    self.assertEqual("0", l_type.integer.minimum_value)
    self.assertEqual("0", l_type.integer.modular_value)

  def test_double_unbounded_both_ends_times_negative_unbounded(self):
    ir = self._make_ir(
        "external Foo:\n"
        "  [requires: (2 * ($static_size_in_bits - $static_size_in_bits) + 1) "
        "             * -$static_size_in_bits > 0]\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].attribute[0].value.expression.function.args[0]
    self.assertEqual("1", expr.type.integer.modulus)
    self.assertEqual("0", expr.type.integer.modular_value)
    self.assertEqual("-infinity", expr.type.integer.minimum_value)
    self.assertEqual("infinity", expr.type.integer.maximum_value)

  def test_choice_two_non_constant_integers(self):
    cases = [
        # t % 12 == 7 and f % 20 == 15 ==> r % 4 == 3
        (12, 7, 20, 15, 4, 3, -128 * 20 + 15, 127 * 20 + 15),
        # t % 24 == 15 and f % 12 == 7 ==> r % 4 == 3
        (24, 15, 12, 7, 4, 3, -128 * 24 + 15, 127 * 24 + 15),
        # t % 20 == 15 and f % 20 == 10 ==> r % 5 == 0
        (20, 15, 20, 10, 5, 0, -128 * 20 + 10, 127 * 20 + 15),
        # t % 20 == 16 and f % 20 == 11 ==> r % 5 == 1
        (20, 16, 20, 11, 5, 1, -128 * 20 + 11, 127 * 20 + 16),
    ]
    for (t_mod, t_val, f_mod, f_val, r_mod, r_val, r_min, r_max) in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0 [+1]    UInt  x\n"
                         "  1 [+1]    Int   y\n"
                         "  if (x == 0 ? y * {} + {} : y * {} + {}) == 0:\n"
                         "    1 [+1]  UInt  z\n".format(
                             t_mod, t_val, f_mod, f_val))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      field = ir.module[0].type[0].structure.field[2]
      expr = field.existence_condition.function.args[0]
      self.assertEqual(str(r_mod), expr.type.integer.modulus)
      self.assertEqual(str(r_val), expr.type.integer.modular_value)
      self.assertEqual(str(r_min), expr.type.integer.minimum_value)
      self.assertEqual(str(r_max), expr.type.integer.maximum_value)

  def test_choice_one_non_constant_integer(self):
    cases = [
        # t == 35 and f % 20 == 15 ==> res % 20 == 15
        (35, 20, 15, 20, 15, -128 * 20 + 15, 127 * 20 + 15),
        # t == 200035 and f % 20 == 15 ==> res % 20 == 15
        (200035, 20, 15, 20, 15, -128 * 20 + 15, 200035),
        # t == 21 and f % 20 == 16 ==> res % 5 == 1
        (21, 20, 16, 5, 1, -128 * 20 + 16, 127 * 20 + 16),
    ]
    for (t_val, f_mod, f_val, r_mod, r_val, r_min, r_max) in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0 [+1]    UInt  x\n"
                         "  1 [+1]    Int   y\n"
                         "  if (x == 0 ? {0} : y * {1} + {2}) == 0:\n"
                         "    1 [+1]  UInt  z\n"
                         "  if (x == 0 ? y * {1} + {2} : {0}) == 0:\n"
                         "    1 [+1]  UInt  q\n".format(t_val, f_mod, f_val))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      field_constant_true = ir.module[0].type[0].structure.field[2]
      constant_true = field_constant_true.existence_condition.function.args[0]
      field_constant_false = ir.module[0].type[0].structure.field[3]
      constant_false = field_constant_false.existence_condition.function.args[0]
      self.assertEqual(str(r_mod), constant_true.type.integer.modulus)
      self.assertEqual(str(r_val), constant_true.type.integer.modular_value)
      self.assertEqual(str(r_min), constant_true.type.integer.minimum_value)
      self.assertEqual(str(r_max), constant_true.type.integer.maximum_value)
      self.assertEqual(str(r_mod), constant_false.type.integer.modulus)
      self.assertEqual(str(r_val), constant_false.type.integer.modular_value)
      self.assertEqual(str(r_min), constant_false.type.integer.minimum_value)
      self.assertEqual(str(r_max), constant_false.type.integer.maximum_value)

  def test_choice_two_constant_integers(self):
    cases = [
        # t == 10 and f == 7 ==> res % 3 == 1
        (10, 7, 3, 1, 7, 10),
        # t == 4 and f == 4 ==> res == 4
        (4, 4, "infinity", 4, 4, 4),
    ]
    for (t_val, f_val, r_mod, r_val, r_min, r_max) in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0 [+1]    UInt  x\n"
                         "  1 [+1]    Int   y\n"
                         "  if (x == 0 ? {} : {}) == 0:\n"
                         "    1 [+1]  UInt  z\n".format(t_val, f_val))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      field_constant_true = ir.module[0].type[0].structure.field[2]
      constant_true = field_constant_true.existence_condition.function.args[0]
      self.assertEqual(str(r_mod), constant_true.type.integer.modulus)
      self.assertEqual(str(r_val), constant_true.type.integer.modular_value)
      self.assertEqual(str(r_min), constant_true.type.integer.minimum_value)
      self.assertEqual(str(r_max), constant_true.type.integer.maximum_value)

  def test_constant_true_has(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if $present(x):\n"
                       "    1 [+1]  UInt  q\n"
                       "  0 [+1]    UInt  x\n"
                       "  if x > 10:\n"
                       "    1 [+1]  Int   y\n"
                       "  if false:\n"
                       "    2 [+1]  Int   z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[0]
    has_func = field.existence_condition
    self.assertTrue(has_func.type.boolean.value)

  def test_constant_false_has(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if $present(z):\n"
                       "    1 [+1]  UInt  q\n"
                       "  0 [+1]    UInt  x\n"
                       "  if x > 10:\n"
                       "    1 [+1]  Int   y\n"
                       "  if false:\n"
                       "    2 [+1]  Int   z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[0]
    has_func = field.existence_condition
    self.assertTrue(has_func.type.boolean.HasField("value"))
    self.assertFalse(has_func.type.boolean.value)

  def test_variable_has(self):
    ir = self._make_ir("struct Foo:\n"
                       "  if $present(y):\n"
                       "    1 [+1]  UInt  q\n"
                       "  0 [+1]    UInt  x\n"
                       "  if x > 10:\n"
                       "    1 [+1]  Int   y\n"
                       "  if false:\n"
                       "    2 [+1]  Int   z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[0]
    has_func = field.existence_condition
    self.assertFalse(has_func.type.boolean.HasField("value"))

  def test_max_of_constants(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  if $max(0, 1, 2) == 0:\n"
                       "    1 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[2]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("infinity", max_func.type.integer.modulus)
    self.assertEqual("2", max_func.type.integer.modular_value)
    self.assertEqual("2", max_func.type.integer.minimum_value)
    self.assertEqual("2", max_func.type.integer.maximum_value)

  def test_max_dominated_by_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  if $max(x, y, 255) == 0:\n"
                       "    1 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[2]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("infinity", max_func.type.integer.modulus)
    self.assertEqual("255", max_func.type.integer.modular_value)
    self.assertEqual("255", max_func.type.integer.minimum_value)
    self.assertEqual("255", max_func.type.integer.maximum_value)

  def test_max_of_variables(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  if $max(x, y) == 0:\n"
                       "    1 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[2]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("1", max_func.type.integer.modulus)
    self.assertEqual("0", max_func.type.integer.modular_value)
    self.assertEqual("0", max_func.type.integer.minimum_value)
    self.assertEqual("255", max_func.type.integer.maximum_value)

  def test_max_of_variables_with_shared_modulus(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  if $max(x * 8 + 5, y * 4 + 3) == 0:\n"
                       "    1 [+1]  UInt  z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[2]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("2", max_func.type.integer.modulus)
    self.assertEqual("1", max_func.type.integer.modular_value)
    self.assertEqual("5", max_func.type.integer.minimum_value)
    self.assertEqual("2045", max_func.type.integer.maximum_value)

  def test_max_of_three_variables(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  2 [+2]    Int   z\n"
                       "  if $max(x, y, z) == 0:\n"
                       "    1 [+1]  UInt  q\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[3]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("1", max_func.type.integer.modulus)
    self.assertEqual("0", max_func.type.integer.modular_value)
    self.assertEqual("0", max_func.type.integer.minimum_value)
    self.assertEqual("32767", max_func.type.integer.maximum_value)

  def test_max_of_one_variable(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  2 [+2]    Int   z\n"
                       "  if $max(x * 2 + 3) == 0:\n"
                       "    1 [+1]  UInt  q\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[3]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("2", max_func.type.integer.modulus)
    self.assertEqual("1", max_func.type.integer.modular_value)
    self.assertEqual("3", max_func.type.integer.minimum_value)
    self.assertEqual("513", max_func.type.integer.maximum_value)

  def test_max_of_one_variable_and_one_constant(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  1 [+1]    Int   y\n"
                       "  2 [+2]    Int   z\n"
                       "  if $max(x * 2 + 3, 311) == 0:\n"
                       "    1 [+1]  UInt  q\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field = ir.module[0].type[0].structure.field[3]
    max_func = field.existence_condition.function.args[0]
    self.assertEqual("2", max_func.type.integer.modulus)
    self.assertEqual("1", max_func.type.integer.modular_value)
    self.assertEqual("311", max_func.type.integer.minimum_value)
    self.assertEqual("513", max_func.type.integer.maximum_value)

  def test_choice_non_integer_arguments(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]    UInt  x\n"
                       "  if x == 0 ? false : true:\n"
                       "    1 [+1]  UInt  y\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    expr = ir.module[0].type[0].structure.field[1].existence_condition
    self.assertEqual("boolean", expr.type.WhichOneof("type"))
    self.assertFalse(expr.type.boolean.HasField("value"))

  def test_uint_value_range_for_explicit_size(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  UInt     x\n"
                       "  1 [+x]  UInt:16  y\n"
                       "  y [+1]  UInt     z\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    z_start = ir.module[0].type[0].structure.field[2].location.start
    self.assertEqual("1", z_start.type.integer.modulus)
    self.assertEqual("0", z_start.type.integer.modular_value)
    self.assertEqual("0", z_start.type.integer.minimum_value)
    self.assertEqual("65535", z_start.type.integer.maximum_value)

  def test_uint_value_ranges(self):
    cases = [
        (1, 1),
        (2, 3),
        (3, 7),
        (4, 15),
        (8, 255),
        (12, 4095),
        (15, 32767),
        (16, 65535),
        (32, 4294967295),
        (48, 281474976710655),
        (64, 18446744073709551615),
    ]
    for bits, upper in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0   [+8]   bits:\n"
                         "    0 [+{}]  UInt  x\n"
                         "  x   [+1]   UInt  z\n".format(bits))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      z_start = ir.module[0].type[0].structure.field[2].location.start
      self.assertEqual("1", z_start.type.integer.modulus)
      self.assertEqual("0", z_start.type.integer.modular_value)
      self.assertEqual("0", z_start.type.integer.minimum_value)
      self.assertEqual(str(upper), z_start.type.integer.maximum_value)

  def test_int_value_ranges(self):
    cases = [
        (1, -1, 0),
        (2, -2, 1),
        (3, -4, 3),
        (4, -8, 7),
        (8, -128, 127),
        (12, -2048, 2047),
        (15, -16384, 16383),
        (16, -32768, 32767),
        (32, -2147483648, 2147483647),
        (48, -140737488355328, 140737488355327),
        (64, -9223372036854775808, 9223372036854775807),
    ]
    for bits, lower, upper in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0   [+8]   bits:\n"
                         "    0 [+{}]  Int   x\n"
                         "  x   [+1]   UInt  z\n".format(bits))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      z_start = ir.module[0].type[0].structure.field[2].location.start
      self.assertEqual("1", z_start.type.integer.modulus)
      self.assertEqual("0", z_start.type.integer.modular_value)
      self.assertEqual(str(lower), z_start.type.integer.minimum_value)
      self.assertEqual(str(upper), z_start.type.integer.maximum_value)

  def test_bcd_value_ranges(self):
    cases = [
        (1, 1),
        (2, 3),
        (3, 7),
        (4, 9),
        (8, 99),
        (12, 999),
        (15, 7999),
        (16, 9999),
        (32, 99999999),
        (48, 999999999999),
        (64, 9999999999999999),
    ]
    for bits, upper in cases:
      ir = self._make_ir("struct Foo:\n"
                         "  0   [+8]   bits:\n"
                         "    0 [+{}]  Bcd   x\n"
                         "  x   [+1]   UInt  z\n".format(bits))
      self.assertEqual([], expression_bounds.compute_constants(ir))
      z_start = ir.module[0].type[0].structure.field[2].location.start
      self.assertEqual("1", z_start.type.integer.modulus)
      self.assertEqual("0", z_start.type.integer.modular_value)
      self.assertEqual("0", z_start.type.integer.minimum_value)
      self.assertEqual(str(upper), z_start.type.integer.maximum_value)

  def test_virtual_field_bounds(self):
    ir = self._make_ir("struct Foo:\n"
                       "  0 [+1]  UInt     x\n"
                       "  let y = x + 10\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field_y = ir.module[0].type[0].structure.field[1]
    self.assertEqual("1", field_y.read_transform.type.integer.modulus)
    self.assertEqual("0", field_y.read_transform.type.integer.modular_value)
    self.assertEqual("10", field_y.read_transform.type.integer.minimum_value)
    self.assertEqual("265", field_y.read_transform.type.integer.maximum_value)

  def test_virtual_field_bounds_copied(self):
    ir = self._make_ir("struct Foo:\n"
                       "  let z = y + 100\n"
                       "  let y = x + 10\n"
                       "  0 [+1]  UInt     x\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field_z = ir.module[0].type[0].structure.field[0]
    self.assertEqual("1", field_z.read_transform.type.integer.modulus)
    self.assertEqual("0", field_z.read_transform.type.integer.modular_value)
    self.assertEqual("110", field_z.read_transform.type.integer.minimum_value)
    self.assertEqual("365", field_z.read_transform.type.integer.maximum_value)
    y_reference = field_z.read_transform.function.args[0]
    self.assertEqual("1", y_reference.type.integer.modulus)
    self.assertEqual("0", y_reference.type.integer.modular_value)
    self.assertEqual("10", y_reference.type.integer.minimum_value)
    self.assertEqual("265", y_reference.type.integer.maximum_value)

  def test_constant_reference_to_virtual_bounds_copied(self):
    ir = self._make_ir("struct Foo:\n"
                       "  let ten = Bar.ten\n"
                       "  let truth = Bar.truth\n"
                       "struct Bar:\n"
                       "  let ten = 10\n"
                       "  let truth = true\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field_ten = ir.module[0].type[0].structure.field[0]
    self.assertEqual("infinity", field_ten.read_transform.type.integer.modulus)
    self.assertEqual("10", field_ten.read_transform.type.integer.modular_value)
    self.assertEqual("10", field_ten.read_transform.type.integer.minimum_value)
    self.assertEqual("10", field_ten.read_transform.type.integer.maximum_value)
    field_truth = ir.module[0].type[0].structure.field[1]
    self.assertTrue(field_truth.read_transform.type.boolean.value)

  def test_forward_reference_to_reference_to_enum_correctly_calculated(self):
    ir = self._make_ir("struct Foo:\n"
                       "  let ten = Bar.TEN\n"
                       "enum Bar:\n"
                       "  TEN = TEN2\n"
                       "  TEN2 = 5 + 5\n")
    self.assertEqual([], expression_bounds.compute_constants(ir))
    field_ten = ir.module[0].type[0].structure.field[0]
    self.assertEqual("10", field_ten.read_transform.type.enumeration.value)


class InfinityAugmentedArithmeticTest(unittest.TestCase):

  # TODO(bolms): Will there ever be any situations where all elements of the arg
  # to _min would be "infinity"?
  def test_min_of_infinities(self):
    self.assertEqual("infinity",
                     expression_bounds._min(["infinity", "infinity"]))

  # TODO(bolms): Will there ever be any situations where all elements of the arg
  # to _max would be "-infinity"?
  def test_max_of_negative_infinities(self):
    self.assertEqual("-infinity",
                     expression_bounds._max(["-infinity", "-infinity"]))

  def test_shared_modular_value_of_identical_modulus_and_value(self):
    self.assertEqual((10, 8),
                     expression_bounds._shared_modular_value((10, 8), (10, 8)))

  def test_shared_modular_value_of_identical_modulus(self):
    self.assertEqual((5, 3),
                     expression_bounds._shared_modular_value((10, 8), (10, 3)))

  def test_shared_modular_value_of_identical_value(self):
    self.assertEqual((6, 2),
                     expression_bounds._shared_modular_value((18, 2), (12, 2)))

  def test_shared_modular_value_of_different_arguments(self):
    self.assertEqual((7, 4),
                     expression_bounds._shared_modular_value((21, 11), (14, 4)))

  def test_shared_modular_value_of_infinity_and_non(self):
    self.assertEqual((7, 4),
                     expression_bounds._shared_modular_value(("infinity", 25),
                                                             (14, 4)))

  def test_shared_modular_value_of_infinity_and_infinity(self):
    self.assertEqual((14, 5),
                     expression_bounds._shared_modular_value(("infinity", 19),
                                                             ("infinity", 5)))

  def test_shared_modular_value_of_infinity_and_identical_value(self):
    self.assertEqual(("infinity", 5),
                     expression_bounds._shared_modular_value(("infinity", 5),
                                                             ("infinity", 5)))


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