# 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 emboss.front_end.symbol_resolver."""

import unittest
from compiler.front_end import glue
from compiler.front_end import symbol_resolver
from compiler.util import error
from compiler.util import test_util

_HAPPY_EMB = """
struct Foo:
  0 [+4]  UInt    uint_field
  4 [+4]  Bar     bar_field
  8 [+16] UInt[4] array_field

struct Bar:
  0 [+4]  Qux     bar

enum Qux:
  ABC = 1
  DEF = 2

struct FieldRef:
  n-4      [+n]       UInt:8[n]       data
  offset-4 [+offset]  UInt:8[offset]  data2
  0        [+4]       UInt            offset (n)

struct VoidLength:
  0 [+10]  UInt:8[]  ten_bytes

enum Quux:
  ABC = 1
  DEF = ABC

struct UsesParameter(x: UInt:8):
  0 [+x]   UInt:8[]  block
"""


class ResolveSymbolsTest(unittest.TestCase):
  """Tests for symbol_resolver.resolve_symbols()."""

  def _construct_ir_multiple(self, file_dict, primary_emb_name):
    ir, unused_debug_info, errors = glue.parse_emboss_file(
        primary_emb_name,
        test_util.dict_file_reader(file_dict),
        stop_before_step="resolve_symbols")
    assert not errors
    return ir

  def _construct_ir(self, emb_text, name="happy.emb"):
    return self._construct_ir_multiple({name: emb_text}, name)

  def test_struct_field_atomic_type_resolution(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[0].structure
    atomic_field1_reference = struct_ir.field[0].type.atomic_type.reference
    self.assertEqual(atomic_field1_reference.canonical_name.object_path, ["UInt"
                                                                         ])
    self.assertEqual(atomic_field1_reference.canonical_name.module_file, "")
    atomic_field2_reference = struct_ir.field[1].type.atomic_type.reference
    self.assertEqual(atomic_field2_reference.canonical_name.object_path, ["Bar"
                                                                         ])
    self.assertEqual(atomic_field2_reference.canonical_name.module_file,
                     "happy.emb")

  def test_struct_field_enum_type_resolution(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[1].structure
    atomic_field_reference = struct_ir.field[0].type.atomic_type.reference
    self.assertEqual(atomic_field_reference.canonical_name.object_path, ["Qux"])
    self.assertEqual(atomic_field_reference.canonical_name.module_file,
                     "happy.emb")

  def test_struct_field_array_type_resolution(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    array_field_type = ir.module[0].type[0].structure.field[2].type.array_type
    array_field_reference = array_field_type.base_type.atomic_type.reference
    self.assertEqual(array_field_reference.canonical_name.object_path, ["UInt"])
    self.assertEqual(array_field_reference.canonical_name.module_file, "")

  def test_inner_type_resolution(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    array_field_type = ir.module[0].type[0].structure.field[2].type.array_type
    array_field_reference = array_field_type.base_type.atomic_type.reference
    self.assertEqual(array_field_reference.canonical_name.object_path, ["UInt"])
    self.assertEqual(array_field_reference.canonical_name.module_file, "")

  def test_struct_field_resolution_in_expression_in_location(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[3].structure
    field0_loc = struct_ir.field[0].location
    abbreviation_reference = field0_loc.size.field_reference.path[0]
    self.assertEqual(abbreviation_reference.canonical_name.object_path,
                     ["FieldRef", "offset"])
    self.assertEqual(abbreviation_reference.canonical_name.module_file,
                     "happy.emb")
    field0_start_left = field0_loc.start.function.args[0]
    nested_abbreviation_reference = field0_start_left.field_reference.path[0]
    self.assertEqual(nested_abbreviation_reference.canonical_name.object_path,
                     ["FieldRef", "offset"])
    self.assertEqual(nested_abbreviation_reference.canonical_name.module_file,
                     "happy.emb")
    field1_loc = struct_ir.field[1].location
    direct_reference = field1_loc.size.field_reference.path[0]
    self.assertEqual(direct_reference.canonical_name.object_path, ["FieldRef",
                                                                   "offset"])
    self.assertEqual(direct_reference.canonical_name.module_file, "happy.emb")
    field1_start_left = field1_loc.start.function.args[0]
    nested_direct_reference = field1_start_left.field_reference.path[0]
    self.assertEqual(nested_direct_reference.canonical_name.object_path,
                     ["FieldRef", "offset"])
    self.assertEqual(nested_direct_reference.canonical_name.module_file,
                     "happy.emb")

  def test_struct_field_resolution_in_expression_in_array_length(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[3].structure
    field0_array_type = struct_ir.field[0].type.array_type
    field0_array_element_count = field0_array_type.element_count
    abbreviation_reference = field0_array_element_count.field_reference.path[0]
    self.assertEqual(abbreviation_reference.canonical_name.object_path,
                     ["FieldRef", "offset"])
    self.assertEqual(abbreviation_reference.canonical_name.module_file,
                     "happy.emb")
    field1_array_type = struct_ir.field[1].type.array_type
    direct_reference = field1_array_type.element_count.field_reference.path[0]
    self.assertEqual(direct_reference.canonical_name.object_path, ["FieldRef",
                                                                   "offset"])
    self.assertEqual(direct_reference.canonical_name.module_file, "happy.emb")

  def test_struct_parameter_resolution(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[6].structure
    size_ir = struct_ir.field[0].location.size
    self.assertTrue(size_ir.HasField("field_reference"))
    self.assertEqual(size_ir.field_reference.path[0].canonical_name.object_path,
                     ["UsesParameter", "x"])

  def test_enum_value_resolution_in_expression_in_enum_field(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    enum_ir = ir.module[0].type[5].enumeration
    value_reference = enum_ir.value[1].value.constant_reference
    self.assertEqual(value_reference.canonical_name.object_path,
                     ["Quux", "ABC"])
    self.assertEqual(value_reference.canonical_name.module_file, "happy.emb")

  def test_symbol_resolution_in_expression_in_void_array_length(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    struct_ir = ir.module[0].type[4].structure
    array_type = struct_ir.field[0].type.array_type
    # The symbol resolver should ignore void fields.
    self.assertEqual("automatic", array_type.WhichOneof("size"))

  def test_name_definitions_have_correct_canonical_names(self):
    ir = self._construct_ir(_HAPPY_EMB)
    self.assertEqual([], symbol_resolver.resolve_symbols(ir))
    foo_name = ir.module[0].type[0].name
    self.assertEqual(foo_name.canonical_name.object_path, ["Foo"])
    self.assertEqual(foo_name.canonical_name.module_file, "happy.emb")
    uint_field_name = ir.module[0].type[0].structure.field[0].name
    self.assertEqual(uint_field_name.canonical_name.object_path, ["Foo",
                                                                  "uint_field"])
    self.assertEqual(uint_field_name.canonical_name.module_file, "happy.emb")
    foo_name = ir.module[0].type[2].name
    self.assertEqual(foo_name.canonical_name.object_path, ["Qux"])
    self.assertEqual(foo_name.canonical_name.module_file, "happy.emb")

  def test_duplicate_type_name(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  UInt  field\n"
                            "struct Foo:\n"
                            "  0 [+4]  UInt  bar\n", "duplicate_type.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    self.assertEqual([
        [error.error("duplicate_type.emb",
                     ir.module[0].type[1].name.source_location,
                     "Duplicate name 'Foo'"),
         error.note("duplicate_type.emb",
                    ir.module[0].type[0].name.source_location,
                    "Original definition")]
    ], errors)

  def test_duplicate_field_name_in_struct(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  UInt  field\n"
                            "  4 [+4]  UInt  field\n", "duplicate_field.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    struct = ir.module[0].type[0].structure
    self.assertEqual([[
        error.error("duplicate_field.emb",
                    struct.field[1].name.source_location,
                    "Duplicate name 'field'"),
        error.note("duplicate_field.emb",
                   struct.field[0].name.source_location,
                   "Original definition")
    ]], errors)

  def test_duplicate_abbreviation_in_struct(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  UInt  field1 (f)\n"
                            "  4 [+4]  UInt  field2 (f)\n",
                            "duplicate_field.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    struct = ir.module[0].type[0].structure
    self.assertEqual([[
        error.error("duplicate_field.emb",
                    struct.field[1].abbreviation.source_location,
                    "Duplicate name 'f'"),
        error.note("duplicate_field.emb",
                   struct.field[0].abbreviation.source_location,
                   "Original definition")
    ]], errors)

  def test_abbreviation_duplicates_field_name_in_struct(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  UInt  field\n"
                            "  4 [+4]  UInt  field2 (field)\n",
                            "duplicate_field.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    struct = ir.module[0].type[0].structure
    self.assertEqual([[
        error.error("duplicate_field.emb",
                    struct.field[1].abbreviation.source_location,
                    "Duplicate name 'field'"),
        error.note("duplicate_field.emb",
                   struct.field[0].name.source_location,
                   "Original definition")
    ]], errors)

  def test_field_name_duplicates_abbreviation_in_struct(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  UInt  field (field2)\n"
                            "  4 [+4]  UInt  field2\n", "duplicate_field.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    struct = ir.module[0].type[0].structure
    self.assertEqual([[
        error.error("duplicate_field.emb",
                    struct.field[1].name.source_location,
                    "Duplicate name 'field2'"),
        error.note("duplicate_field.emb",
                   struct.field[0].abbreviation.source_location,
                   "Original definition")
    ]], errors)

  def test_duplicate_value_name_in_enum(self):
    ir = self._construct_ir("enum Foo:\n"
                            "  BAR = 1\n"
                            "  BAR = 1\n", "duplicate_enum.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    self.assertEqual([[
        error.error(
            "duplicate_enum.emb",
            ir.module[0].type[0].enumeration.value[1].name.source_location,
            "Duplicate name 'BAR'"),
        error.note(
            "duplicate_enum.emb",
            ir.module[0].type[0].enumeration.value[0].name.source_location,
            "Original definition")
    ]], errors)

  def test_ambiguous_name(self):
    # struct UInt will be ambiguous with the external UInt in the prelude.
    ir = self._construct_ir("struct UInt:\n"
                            "  0 [+4]  Int:8[4]  field\n"
                            "struct Foo:\n"
                            "  0 [+4]  UInt  bar\n", "ambiguous.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    # Find the UInt definition in the prelude.
    for type_ir in ir.module[1].type:
      if type_ir.name.name.text == "UInt":
        prelude_uint = type_ir
        break
    ambiguous_type_ir = ir.module[0].type[1].structure.field[0].type.atomic_type
    self.assertEqual([[
        error.error("ambiguous.emb",
                    ambiguous_type_ir.reference.source_name[0].source_location,
                    "Ambiguous name 'UInt'"), error.note(
                        "", prelude_uint.name.source_location,
                        "Possible resolution"),
        error.note("ambiguous.emb", ir.module[0].type[0].name.source_location,
                   "Possible resolution")
    ]], errors)

  def test_missing_name(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+4]  Bar  field\n",
                            "missing.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    missing_type_ir = ir.module[0].type[0].structure.field[0].type.atomic_type
    self.assertEqual([
        [error.error("missing.emb",
                     missing_type_ir.reference.source_name[0].source_location,
                     "No candidate for 'Bar'")]
    ], errors)

  def test_missing_leading_name(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+Num.FOUR]  UInt  field\n", "missing.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    missing_expr_ir = ir.module[0].type[0].structure.field[0].location.size
    self.assertEqual([
        [error.error(
            "missing.emb",
            missing_expr_ir.constant_reference.source_name[0].source_location,
            "No candidate for 'Num'")]
    ], errors)

  def test_missing_trailing_name(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+Num.FOUR]  UInt  field\n"
                            "enum Num:\n"
                            "  THREE = 3\n", "missing.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    missing_expr_ir = ir.module[0].type[0].structure.field[0].location.size
    self.assertEqual([
        [error.error(
            "missing.emb",
            missing_expr_ir.constant_reference.source_name[1].source_location,
            "No candidate for 'FOUR'")]
    ], errors)

  def test_missing_middle_name(self):
    ir = self._construct_ir("struct Foo:\n"
                            "  0 [+Num.NaN.FOUR]  UInt  field\n"
                            "enum Num:\n"
                            "  FOUR = 4\n", "missing.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    missing_expr_ir = ir.module[0].type[0].structure.field[0].location.size
    self.assertEqual([
        [error.error(
            "missing.emb",
            missing_expr_ir.constant_reference.source_name[1].source_location,
            "No candidate for 'NaN'")]
    ], errors)

  def test_inner_resolution(self):
    ir = self._construct_ir(
        "struct OuterStruct:\n"
        "\n"
        "  struct InnerStruct2:\n"
        "    0 [+1]  InnerStruct.InnerEnum  inner_enum\n"
        "\n"
        "  struct InnerStruct:\n"
        "    enum InnerEnum:\n"
        "      ONE = 1\n"
        "\n"
        "    0 [+1]  InnerEnum  inner_enum\n"
        "\n"
        "  0 [+InnerStruct.InnerEnum.ONE]  InnerStruct.InnerEnum  inner_enum\n",
        "nested.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertFalse(errors)
    outer_struct = ir.module[0].type[0]
    inner_struct = outer_struct.subtype[1]
    inner_struct_2 = outer_struct.subtype[0]
    inner_enum = inner_struct.subtype[0]
    self.assertEqual(["OuterStruct", "InnerStruct"],
                     list(inner_struct.name.canonical_name.object_path))
    self.assertEqual(["OuterStruct", "InnerStruct", "InnerEnum"],
                     list(inner_enum.name.canonical_name.object_path))
    self.assertEqual(["OuterStruct", "InnerStruct2"],
                     list(inner_struct_2.name.canonical_name.object_path))
    outer_field = outer_struct.structure.field[0]
    outer_field_end_ref = outer_field.location.size.constant_reference
    self.assertEqual(
        ["OuterStruct", "InnerStruct", "InnerEnum", "ONE"], list(
            outer_field_end_ref.canonical_name.object_path))
    self.assertEqual(
        ["OuterStruct", "InnerStruct", "InnerEnum"],
        list(outer_field.type.atomic_type.reference.canonical_name.object_path))
    inner_field_2_type = inner_struct_2.structure.field[0].type.atomic_type
    self.assertEqual(
        ["OuterStruct", "InnerStruct", "InnerEnum"
        ], list(inner_field_2_type.reference.canonical_name.object_path))

  def test_resolution_against_anonymous_bits(self):
    ir = self._construct_ir("struct Struct:\n"
                            "  0 [+1]  bits:\n"
                            "    7 [+1]  Flag  last_packet\n"
                            "    5 [+2]  enum  inline_inner_enum:\n"
                            "      AA = 0\n"
                            "      BB = 1\n"
                            "      CC = 2\n"
                            "      DD = 3\n"
                            "    0 [+5]  UInt  header_size (h)\n"
                            "  0 [+h]  UInt:8[]  header_bytes\n"
                            "\n"
                            "struct Struct2:\n"
                            "  0 [+1]  Struct.InlineInnerEnum  value\n",
                            "anonymity.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertFalse(errors)
    struct1 = ir.module[0].type[0]
    struct1_bits_field = struct1.structure.field[0]
    struct1_bits_field_type = struct1_bits_field.type.atomic_type.reference
    struct1_byte_field = struct1.structure.field[4]
    inner_bits = struct1.subtype[0]
    inner_enum = struct1.subtype[1]
    self.assertTrue(inner_bits.HasField("structure"))
    self.assertTrue(inner_enum.HasField("enumeration"))
    self.assertTrue(inner_bits.name.is_anonymous)
    self.assertFalse(inner_enum.name.is_anonymous)
    self.assertEqual(["Struct", "InlineInnerEnum"],
                     list(inner_enum.name.canonical_name.object_path))
    self.assertEqual(
        ["Struct", "InlineInnerEnum", "AA"],
        list(inner_enum.enumeration.value[0].name.canonical_name.object_path))
    self.assertEqual(
        list(inner_bits.name.canonical_name.object_path),
        list(struct1_bits_field_type.canonical_name.object_path))
    self.assertEqual(2, len(inner_bits.name.canonical_name.object_path))
    self.assertEqual(
        ["Struct", "header_size"],
        list(struct1_byte_field.location.size.field_reference.path[0].
             canonical_name.object_path))

  def test_duplicate_name_in_different_inline_bits(self):
    ir = self._construct_ir(
        "struct Struct:\n"
        "  0 [+1]  bits:\n"
        "    7 [+1]  Flag  a\n"
        "  1 [+1]  bits:\n"
        "    0 [+1]  Flag  a\n", "duplicate_in_anon.emb")
    errors = error.filter_errors(symbol_resolver.resolve_symbols(ir))
    supertype = ir.module[0].type[0]
    self.assertEqual([[
        error.error(
            "duplicate_in_anon.emb",
            supertype.structure.field[3].name.source_location,
            "Duplicate name 'a'"),
        error.note(
            "duplicate_in_anon.emb",
            supertype.structure.field[1].name.source_location,
            "Original definition")
    ]], errors)

  def test_duplicate_name_in_same_inline_bits(self):
    ir = self._construct_ir(
        "struct Struct:\n"
        "  0 [+1]  bits:\n"
        "    7 [+1]  Flag  a\n"
        "    0 [+1]  Flag  a\n", "duplicate_in_anon.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    supertype = ir.module[0].type[0]
    self.assertEqual([[
        error.error(
            "duplicate_in_anon.emb",
            supertype.structure.field[2].name.source_location,
            "Duplicate name 'a'"),
        error.note(
            "duplicate_in_anon.emb",
            supertype.structure.field[1].name.source_location,
            "Original definition")
    ]], error.filter_errors(errors))

  def test_import_type_resolution(self):
    importer = ('import "ed.emb" as ed\n'
                "struct Ff:\n"
                "  0 [+1]  ed.Gg  gg\n")
    imported = ("struct Gg:\n"
                "  0 [+1]  UInt  qq\n")
    ir = self._construct_ir_multiple({"ed.emb": imported, "er.emb": importer},
                                     "er.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertEqual([], errors)

  def test_duplicate_import_name(self):
    importer = ('import "ed.emb" as ed\n'
                'import "ed.emb" as ed\n'
                "struct Ff:\n"
                "  0 [+1]  ed.Gg  gg\n")
    imported = ("struct Gg:\n"
                "  0 [+1]  UInt  qq\n")
    ir = self._construct_ir_multiple({"ed.emb": imported, "er.emb": importer},
                                     "er.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    # Note: the error is on import[2] duplicating import[1] because the implicit
    # prelude import is import[0].
    self.assertEqual([
        [error.error("er.emb",
                     ir.module[0].foreign_import[2].local_name.source_location,
                     "Duplicate name 'ed'"),
         error.note("er.emb",
                    ir.module[0].foreign_import[1].local_name.source_location,
                    "Original definition")]
    ], errors)

  def test_import_enum_resolution(self):
    importer = ('import "ed.emb" as ed\n'
                "struct Ff:\n"
                "  if ed.Gg.GG == ed.Gg.GG:\n"
                "    0 [+1]  UInt  gg\n")
    imported = ("enum Gg:\n"
                "  GG = 0\n")
    ir = self._construct_ir_multiple({"ed.emb": imported, "er.emb": importer},
                                     "er.emb")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertEqual([], errors)

  def test_that_double_import_names_are_syntactically_invalid(self):
    # There are currently no checks in resolve_symbols that it is not possible
    # to get to symbols imported by another module, because it is syntactically
    # invalid.  This may change in the future, in which case this test should be
    # fixed by adding an explicit check to resolve_symbols and checking the
    # error message here.
    importer = ('import "ed.emb" as ed\n'
                "struct Ff:\n"
                "  0 [+1]  ed.ed2.Gg  gg\n")
    imported = 'import "ed2.emb" as ed2\n'
    imported2 = ("struct Gg:\n"
                 "  0 [+1]  UInt  qq\n")
    unused_ir, unused_debug_info, errors = glue.parse_emboss_file(
        "er.emb",
        test_util.dict_file_reader({"ed.emb": imported,
                                    "ed2.emb": imported2,
                                    "er.emb": importer}),
        stop_before_step="resolve_symbols")
    assert errors

  def test_no_error_when_inline_name_aliases_outer_name(self):
    # The inline enum's complete type should be Foo.Foo.  During parsing, the
    # name is set to just "Foo", but symbol resolution should a) select the
    # correct Foo, and b) not complain that multiple Foos could match.
    ir = self._construct_ir(
        "struct Foo:\n"
        "  0 [+1]  enum  foo:\n"
        "    BAR = 0\n")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertEqual([], errors)
    field = ir.module[0].type[0].structure.field[0]
    self.assertEqual(
        ["Foo", "Foo"],
        list(field.type.atomic_type.reference.canonical_name.object_path))

  def test_no_error_when_inline_name_in_anonymous_bits_aliases_outer_name(self):
    # There is an extra layer of complexity when an inline type appears inside
    # of an inline bits.
    ir = self._construct_ir(
        "struct Foo:\n"
        "  0 [+1]  bits:\n"
        "    0 [+4]  enum  foo:\n"
        "      BAR = 0\n")
    errors = symbol_resolver.resolve_symbols(ir)
    self.assertEqual([], error.filter_errors(errors))
    field = ir.module[0].type[0].subtype[0].structure.field[0]
    self.assertEqual(
        ["Foo", "Foo"],
        list(field.type.atomic_type.reference.canonical_name.object_path))


class ResolveFieldReferencesTest(unittest.TestCase):
  """Tests for symbol_resolver.resolve_field_references()."""

  def _construct_ir_multiple(self, file_dict, primary_emb_name):
    ir, unused_debug_info, errors = glue.parse_emboss_file(
        primary_emb_name,
        test_util.dict_file_reader(file_dict),
        stop_before_step="resolve_field_references")
    assert not errors
    return ir

  def _construct_ir(self, emb_text, name="happy.emb"):
    return self._construct_ir_multiple({name: emb_text}, name)

  def test_subfield_resolution(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg        gg\n"
        "  1 [+gg.qq]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    qq\n", "subfield.emb")
    errors = symbol_resolver.resolve_field_references(ir)
    self.assertFalse(errors)
    ff = ir.module[0].type[0]
    location_end_path = ff.structure.field[1].location.size.field_reference.path
    self.assertEqual(["Ff", "gg"],
                     list(location_end_path[0].canonical_name.object_path))
    self.assertEqual(["Gg", "qq"],
                     list(location_end_path[1].canonical_name.object_path))

  def test_aliased_subfield_resolution(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg        real_gg\n"
        "  1 [+gg.qq]  UInt:8[]  data\n"
        "  let gg = real_gg\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    real_qq\n"
        "  let qq = real_qq", "subfield.emb")
    errors = symbol_resolver.resolve_field_references(ir)
    self.assertFalse(errors)
    ff = ir.module[0].type[0]
    location_end_path = ff.structure.field[1].location.size.field_reference.path
    self.assertEqual(["Ff", "gg"],
                     list(location_end_path[0].canonical_name.object_path))
    self.assertEqual(["Gg", "qq"],
                     list(location_end_path[1].canonical_name.object_path))

  def test_aliased_aliased_subfield_resolution(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg        really_real_gg\n"
        "  1 [+gg.qq]  UInt:8[]  data\n"
        "  let gg = real_gg\n"
        "  let real_gg = really_real_gg\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    qq\n", "subfield.emb")
    errors = symbol_resolver.resolve_field_references(ir)
    self.assertFalse(errors)
    ff = ir.module[0].type[0]
    location_end_path = ff.structure.field[1].location.size.field_reference.path
    self.assertEqual(["Ff", "gg"],
                     list(location_end_path[0].canonical_name.object_path))
    self.assertEqual(["Gg", "qq"],
                     list(location_end_path[1].canonical_name.object_path))

  def test_subfield_resolution_fails(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg        gg\n"
        "  1 [+gg.rr]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    qq\n", "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    self.assertEqual([
        [error.error("subfield.emb", ir.module[0].type[0].structure.field[
            1].location.size.field_reference.path[1].source_name[
                0].source_location, "No candidate for 'rr'")]
    ], errors)

  def test_subfield_resolution_failure_shortcuts_further_resolution(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]         Gg        gg\n"
        "  1 [+gg.rr.qq]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]         UInt    qq\n", "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    self.assertEqual([
        [error.error("subfield.emb", ir.module[0].type[0].structure.field[
            1].location.size.field_reference.path[1].source_name[
                0].source_location, "No candidate for 'rr'")]
    ], errors)

  def test_subfield_resolution_failure_with_aliased_name(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg        gg\n"
        "  1 [+gg.gg]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    qq\n", "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    self.assertEqual([
        [error.error("subfield.emb", ir.module[0].type[0].structure.field[
            1].location.size.field_reference.path[1].source_name[
                0].source_location, "No candidate for 'gg'")]
    ], errors)

  def test_subfield_resolution_failure_with_array(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      Gg[1]     gg\n"
        "  1 [+gg.qq]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]      UInt    qq\n", "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    self.assertEqual([
        [error.error("subfield.emb", ir.module[0].type[0].structure.field[
            1].location.size.field_reference.path[0].source_name[
                0].source_location, "Cannot access member of array 'gg'")]
    ], errors)

  def test_subfield_resolution_failure_with_int(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]      UInt      gg_source\n"
        "  1 [+gg.qq]  UInt:8[]  data\n"
        "  let gg = gg_source + 1\n",
        "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    error_field = ir.module[0].type[0].structure.field[1]
    error_reference = error_field.location.size.field_reference
    error_location = error_reference.path[0].source_name[0].source_location
    self.assertEqual([
        [error.error("subfield.emb", error_location,
                     "Cannot access member of noncomposite field 'gg'")]
    ], errors)

  def test_subfield_resolution_failure_with_int_no_cascade(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]    UInt      gg_source\n"
        "  1 [+qqx]  UInt:8[]  data\n"
        "  let gg = gg_source + 1\n"
        "  let yy = gg.no_field\n"
        "  let qqx = yy.x\n"
        "  let qqy = yy.y\n",
        "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    error_field = ir.module[0].type[0].structure.field[3]
    error_reference = error_field.read_transform.field_reference
    error_location = error_reference.path[0].source_name[0].source_location
    self.assertEqual([
        [error.error("subfield.emb", error_location,
                     "Cannot access member of noncomposite field 'gg'")]
    ], errors)

  def test_subfield_resolution_failure_with_abbreviation(self):
    ir = self._construct_ir(
        "struct Ff:\n"
        "  0 [+1]     Gg        gg\n"
        "  1 [+gg.q]  UInt:8[]  data\n"
        "struct Gg:\n"
        "  0 [+1]     UInt    qq (q)\n", "subfield.emb")
    errors = error.filter_errors(symbol_resolver.resolve_field_references(ir))
    self.assertEqual([
        # TODO(bolms): Make the error message clearer, in this case.
        [error.error("subfield.emb", ir.module[0].type[0].structure.field[
            1].location.size.field_reference.path[1].source_name[
                0].source_location, "No candidate for 'q'")]
    ], errors)


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