xref: /aosp_15_r20/external/emboss/compiler/front_end/symbol_resolver.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1*99e0aae7SDavid Rees# Copyright 2019 Google LLC
2*99e0aae7SDavid Rees#
3*99e0aae7SDavid Rees# Licensed under the Apache License, Version 2.0 (the "License");
4*99e0aae7SDavid Rees# you may not use this file except in compliance with the License.
5*99e0aae7SDavid Rees# You may obtain a copy of the License at
6*99e0aae7SDavid Rees#
7*99e0aae7SDavid Rees#     https://www.apache.org/licenses/LICENSE-2.0
8*99e0aae7SDavid Rees#
9*99e0aae7SDavid Rees# Unless required by applicable law or agreed to in writing, software
10*99e0aae7SDavid Rees# distributed under the License is distributed on an "AS IS" BASIS,
11*99e0aae7SDavid Rees# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*99e0aae7SDavid Rees# See the License for the specific language governing permissions and
13*99e0aae7SDavid Rees# limitations under the License.
14*99e0aae7SDavid Rees
15*99e0aae7SDavid Rees"""Symbol resolver for Emboss IR.
16*99e0aae7SDavid Rees
17*99e0aae7SDavid ReesThe resolve_symbols function should be used to generate canonical resolutions
18*99e0aae7SDavid Reesfor all symbol references in an Emboss IR.
19*99e0aae7SDavid Rees"""
20*99e0aae7SDavid Rees
21*99e0aae7SDavid Reesimport collections
22*99e0aae7SDavid Rees
23*99e0aae7SDavid Reesfrom compiler.util import error
24*99e0aae7SDavid Reesfrom compiler.util import ir_data
25*99e0aae7SDavid Reesfrom compiler.util import ir_data_utils
26*99e0aae7SDavid Reesfrom compiler.util import ir_util
27*99e0aae7SDavid Reesfrom compiler.util import traverse_ir
28*99e0aae7SDavid Rees
29*99e0aae7SDavid Rees# TODO(bolms): Symbol resolution raises an exception at the first error, but
30*99e0aae7SDavid Rees# this is one place where it can make sense to report multiple errors.
31*99e0aae7SDavid Rees
32*99e0aae7SDavid ReesFileLocation = collections.namedtuple("FileLocation", ["file", "location"])
33*99e0aae7SDavid Rees
34*99e0aae7SDavid Rees
35*99e0aae7SDavid Reesdef ambiguous_name_error(file_name, location, name, candidate_locations):
36*99e0aae7SDavid Rees  """A name cannot be resolved because there are two or more candidates."""
37*99e0aae7SDavid Rees  result = [error.error(file_name, location, "Ambiguous name '{}'".format(name))
38*99e0aae7SDavid Rees           ]
39*99e0aae7SDavid Rees  for location in sorted(candidate_locations):
40*99e0aae7SDavid Rees    result.append(error.note(location.file, location.location,
41*99e0aae7SDavid Rees                             "Possible resolution"))
42*99e0aae7SDavid Rees  return result
43*99e0aae7SDavid Rees
44*99e0aae7SDavid Rees
45*99e0aae7SDavid Reesdef duplicate_name_error(file_name, location, name, original_location):
46*99e0aae7SDavid Rees  """A name is defined two or more times."""
47*99e0aae7SDavid Rees  return [error.error(file_name, location, "Duplicate name '{}'".format(name)),
48*99e0aae7SDavid Rees          error.note(original_location.file, original_location.location,
49*99e0aae7SDavid Rees                     "Original definition")]
50*99e0aae7SDavid Rees
51*99e0aae7SDavid Rees
52*99e0aae7SDavid Reesdef missing_name_error(file_name, location, name):
53*99e0aae7SDavid Rees  return [error.error(file_name, location, "No candidate for '{}'".format(name))
54*99e0aae7SDavid Rees         ]
55*99e0aae7SDavid Rees
56*99e0aae7SDavid Rees
57*99e0aae7SDavid Reesdef array_subfield_error(file_name, location, name):
58*99e0aae7SDavid Rees  return [error.error(file_name, location,
59*99e0aae7SDavid Rees                      "Cannot access member of array '{}'".format(name))]
60*99e0aae7SDavid Rees
61*99e0aae7SDavid Rees
62*99e0aae7SDavid Reesdef noncomposite_subfield_error(file_name, location, name):
63*99e0aae7SDavid Rees  return [error.error(file_name, location,
64*99e0aae7SDavid Rees                      "Cannot access member of noncomposite field '{}'".format(
65*99e0aae7SDavid Rees                          name))]
66*99e0aae7SDavid Rees
67*99e0aae7SDavid Rees
68*99e0aae7SDavid Reesdef _nested_name(canonical_name, name):
69*99e0aae7SDavid Rees  """Creates a new CanonicalName with name appended to the object_path."""
70*99e0aae7SDavid Rees  return ir_data.CanonicalName(
71*99e0aae7SDavid Rees      module_file=canonical_name.module_file,
72*99e0aae7SDavid Rees      object_path=list(canonical_name.object_path) + [name])
73*99e0aae7SDavid Rees
74*99e0aae7SDavid Rees
75*99e0aae7SDavid Reesclass _Scope(dict):
76*99e0aae7SDavid Rees  """A _Scope holds data for a symbol.
77*99e0aae7SDavid Rees
78*99e0aae7SDavid Rees  A _Scope is a dict with some additional attributes.  Lexically nested names
79*99e0aae7SDavid Rees  are kept in the dict, and bookkeeping is kept in the additional attributes.
80*99e0aae7SDavid Rees
81*99e0aae7SDavid Rees  For example, each module should have a child _Scope for each type contained in
82*99e0aae7SDavid Rees  the module.  `struct` and `bits` types should have nested _Scopes for each
83*99e0aae7SDavid Rees  field; `enum` types should have nested scopes for each enumerated name.
84*99e0aae7SDavid Rees
85*99e0aae7SDavid Rees  Attributes:
86*99e0aae7SDavid Rees    canonical_name: The absolute name of this symbol; e.g. ("file.emb",
87*99e0aae7SDavid Rees      "TypeName", "SubTypeName", "field_name")
88*99e0aae7SDavid Rees    source_location: The ir_data.SourceLocation where this symbol is defined.
89*99e0aae7SDavid Rees    visibility: LOCAL, PRIVATE, or SEARCHABLE; see below.
90*99e0aae7SDavid Rees    alias: If set, this name is merely a pointer to another name.
91*99e0aae7SDavid Rees  """
92*99e0aae7SDavid Rees  __slots__ = ("canonical_name", "source_location", "visibility", "alias")
93*99e0aae7SDavid Rees
94*99e0aae7SDavid Rees  # A LOCAL name is visible outside of its enclosing scope, but should not be
95*99e0aae7SDavid Rees  # found when searching for a name.  That is, this name should be matched in
96*99e0aae7SDavid Rees  # the tail of a qualified reference (the 'bar' in 'foo.bar'), but not when
97*99e0aae7SDavid Rees  # searching for names (the 'foo' in 'foo.bar' should not match outside of
98*99e0aae7SDavid Rees  # 'foo's scope).  This applies to public field names.
99*99e0aae7SDavid Rees  LOCAL = object()
100*99e0aae7SDavid Rees
101*99e0aae7SDavid Rees  # A PRIVATE name is similar to LOCAL except that it is never visible outside
102*99e0aae7SDavid Rees  # its enclosing scope.  This applies to abbreviations of field names: if 'a'
103*99e0aae7SDavid Rees  # is an abbreviation for field 'apple', then 'foo.a' is not a valid reference;
104*99e0aae7SDavid Rees  # instead it should be 'foo.apple'.
105*99e0aae7SDavid Rees  PRIVATE = object()
106*99e0aae7SDavid Rees
107*99e0aae7SDavid Rees  # A SEARCHABLE name is visible as long as it is in a scope in the search list.
108*99e0aae7SDavid Rees  # This applies to type names ('Foo'), which may be found from many scopes.
109*99e0aae7SDavid Rees  SEARCHABLE = object()
110*99e0aae7SDavid Rees
111*99e0aae7SDavid Rees  def __init__(self, canonical_name, source_location, visibility, alias=None):
112*99e0aae7SDavid Rees    super(_Scope, self).__init__()
113*99e0aae7SDavid Rees    self.canonical_name = canonical_name
114*99e0aae7SDavid Rees    self.source_location = source_location
115*99e0aae7SDavid Rees    self.visibility = visibility
116*99e0aae7SDavid Rees    self.alias = alias
117*99e0aae7SDavid Rees
118*99e0aae7SDavid Rees
119*99e0aae7SDavid Reesdef _add_name_to_scope(name_ir, scope, canonical_name, visibility, errors):
120*99e0aae7SDavid Rees  """Adds the given name_ir to the given scope."""
121*99e0aae7SDavid Rees  name = name_ir.text
122*99e0aae7SDavid Rees  new_scope = _Scope(canonical_name, name_ir.source_location, visibility)
123*99e0aae7SDavid Rees  if name in scope:
124*99e0aae7SDavid Rees    errors.append(duplicate_name_error(
125*99e0aae7SDavid Rees        scope.canonical_name.module_file, name_ir.source_location, name,
126*99e0aae7SDavid Rees        FileLocation(scope[name].canonical_name.module_file,
127*99e0aae7SDavid Rees                     scope[name].source_location)))
128*99e0aae7SDavid Rees  else:
129*99e0aae7SDavid Rees    scope[name] = new_scope
130*99e0aae7SDavid Rees  return new_scope
131*99e0aae7SDavid Rees
132*99e0aae7SDavid Rees
133*99e0aae7SDavid Reesdef _add_name_to_scope_and_normalize(name_ir, scope, visibility, errors):
134*99e0aae7SDavid Rees  """Adds the given name_ir to scope and sets its canonical_name."""
135*99e0aae7SDavid Rees  name = name_ir.name.text
136*99e0aae7SDavid Rees  canonical_name = _nested_name(scope.canonical_name, name)
137*99e0aae7SDavid Rees  ir_data_utils.builder(name_ir).canonical_name.CopyFrom(canonical_name)
138*99e0aae7SDavid Rees  return _add_name_to_scope(name_ir.name, scope, canonical_name, visibility,
139*99e0aae7SDavid Rees                            errors)
140*99e0aae7SDavid Rees
141*99e0aae7SDavid Rees
142*99e0aae7SDavid Reesdef _add_struct_field_to_scope(field, scope, errors):
143*99e0aae7SDavid Rees  """Adds the name of the given field to the scope."""
144*99e0aae7SDavid Rees  new_scope = _add_name_to_scope_and_normalize(field.name, scope, _Scope.LOCAL,
145*99e0aae7SDavid Rees                                               errors)
146*99e0aae7SDavid Rees  if field.HasField("abbreviation"):
147*99e0aae7SDavid Rees    _add_name_to_scope(field.abbreviation, scope, new_scope.canonical_name,
148*99e0aae7SDavid Rees                       _Scope.PRIVATE, errors)
149*99e0aae7SDavid Rees
150*99e0aae7SDavid Rees  value_builtin_name = ir_data.Word(
151*99e0aae7SDavid Rees      text="this",
152*99e0aae7SDavid Rees      source_location=ir_data.Location(is_synthetic=True),
153*99e0aae7SDavid Rees  )
154*99e0aae7SDavid Rees  # In "inside field" scope, the name `this` maps back to the field itself.
155*99e0aae7SDavid Rees  # This is important for attributes like `[requires]`.
156*99e0aae7SDavid Rees  _add_name_to_scope(value_builtin_name, new_scope,
157*99e0aae7SDavid Rees                     field.name.canonical_name, _Scope.PRIVATE, errors)
158*99e0aae7SDavid Rees
159*99e0aae7SDavid Rees
160*99e0aae7SDavid Reesdef _add_parameter_name_to_scope(parameter, scope, errors):
161*99e0aae7SDavid Rees  """Adds the name of the given parameter to the scope."""
162*99e0aae7SDavid Rees  _add_name_to_scope_and_normalize(parameter.name, scope, _Scope.LOCAL, errors)
163*99e0aae7SDavid Rees
164*99e0aae7SDavid Rees
165*99e0aae7SDavid Reesdef _add_enum_value_to_scope(value, scope, errors):
166*99e0aae7SDavid Rees  """Adds the name of the enum value to scope."""
167*99e0aae7SDavid Rees  _add_name_to_scope_and_normalize(value.name, scope, _Scope.LOCAL, errors)
168*99e0aae7SDavid Rees
169*99e0aae7SDavid Rees
170*99e0aae7SDavid Reesdef _add_type_name_to_scope(type_definition, scope, errors):
171*99e0aae7SDavid Rees  """Adds the name of type_definition to the given scope."""
172*99e0aae7SDavid Rees  new_scope = _add_name_to_scope_and_normalize(type_definition.name, scope,
173*99e0aae7SDavid Rees                                               _Scope.SEARCHABLE, errors)
174*99e0aae7SDavid Rees  return {"scope": new_scope}
175*99e0aae7SDavid Rees
176*99e0aae7SDavid Rees
177*99e0aae7SDavid Reesdef _set_scope_for_type_definition(type_definition, scope):
178*99e0aae7SDavid Rees  """Sets the current scope for an ir_data.AddressableUnit."""
179*99e0aae7SDavid Rees  return {"scope": scope[type_definition.name.name.text]}
180*99e0aae7SDavid Rees
181*99e0aae7SDavid Rees
182*99e0aae7SDavid Reesdef _add_module_to_scope(module, scope):
183*99e0aae7SDavid Rees  """Adds the name of the module to the given scope."""
184*99e0aae7SDavid Rees  module_symbol_table = _Scope(
185*99e0aae7SDavid Rees      ir_data.CanonicalName(module_file=module.source_file_name,
186*99e0aae7SDavid Rees                           object_path=[]),
187*99e0aae7SDavid Rees      None,
188*99e0aae7SDavid Rees      _Scope.SEARCHABLE)
189*99e0aae7SDavid Rees  scope[module.source_file_name] = module_symbol_table
190*99e0aae7SDavid Rees  return {"scope": scope[module.source_file_name]}
191*99e0aae7SDavid Rees
192*99e0aae7SDavid Rees
193*99e0aae7SDavid Reesdef _set_scope_for_module(module, scope):
194*99e0aae7SDavid Rees  """Adds the name of the module to the given scope."""
195*99e0aae7SDavid Rees  return {"scope": scope[module.source_file_name]}
196*99e0aae7SDavid Rees
197*99e0aae7SDavid Rees
198*99e0aae7SDavid Reesdef _add_import_to_scope(foreign_import, table, module, errors):
199*99e0aae7SDavid Rees  if not foreign_import.local_name.text:
200*99e0aae7SDavid Rees    # This is the prelude import; ignore it.
201*99e0aae7SDavid Rees    return
202*99e0aae7SDavid Rees  _add_alias_to_scope(foreign_import.local_name, table, module.canonical_name,
203*99e0aae7SDavid Rees                      [foreign_import.file_name.text], _Scope.SEARCHABLE,
204*99e0aae7SDavid Rees                      errors)
205*99e0aae7SDavid Rees
206*99e0aae7SDavid Rees
207*99e0aae7SDavid Reesdef _construct_symbol_tables(ir):
208*99e0aae7SDavid Rees  """Constructs per-module symbol tables for each module in ir."""
209*99e0aae7SDavid Rees  symbol_tables = {}
210*99e0aae7SDavid Rees  errors = []
211*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
212*99e0aae7SDavid Rees      ir, [ir_data.Module], _add_module_to_scope,
213*99e0aae7SDavid Rees      parameters={"errors": errors, "scope": symbol_tables})
214*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
215*99e0aae7SDavid Rees      ir, [ir_data.TypeDefinition], _add_type_name_to_scope,
216*99e0aae7SDavid Rees      incidental_actions={ir_data.Module: _set_scope_for_module},
217*99e0aae7SDavid Rees      parameters={"errors": errors, "scope": symbol_tables})
218*99e0aae7SDavid Rees  if errors:
219*99e0aae7SDavid Rees    # Ideally, we would find duplicate field names elsewhere in the module, even
220*99e0aae7SDavid Rees    # if there are duplicate type names, but field/enum names in the colliding
221*99e0aae7SDavid Rees    # types also end up colliding, leading to spurious errors.  E.g., if you
222*99e0aae7SDavid Rees    # have two `struct Foo`s, then the field check will also discover a
223*99e0aae7SDavid Rees    # collision for `$size_in_bytes`, since there are two `Foo.$size_in_bytes`.
224*99e0aae7SDavid Rees    return symbol_tables, errors
225*99e0aae7SDavid Rees
226*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
227*99e0aae7SDavid Rees      ir, [ir_data.EnumValue], _add_enum_value_to_scope,
228*99e0aae7SDavid Rees      incidental_actions={
229*99e0aae7SDavid Rees          ir_data.Module: _set_scope_for_module,
230*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_scope_for_type_definition,
231*99e0aae7SDavid Rees      },
232*99e0aae7SDavid Rees      parameters={"errors": errors, "scope": symbol_tables})
233*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
234*99e0aae7SDavid Rees      ir, [ir_data.Field], _add_struct_field_to_scope,
235*99e0aae7SDavid Rees      incidental_actions={
236*99e0aae7SDavid Rees          ir_data.Module: _set_scope_for_module,
237*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_scope_for_type_definition,
238*99e0aae7SDavid Rees      },
239*99e0aae7SDavid Rees      parameters={"errors": errors, "scope": symbol_tables})
240*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
241*99e0aae7SDavid Rees      ir, [ir_data.RuntimeParameter], _add_parameter_name_to_scope,
242*99e0aae7SDavid Rees      incidental_actions={
243*99e0aae7SDavid Rees          ir_data.Module: _set_scope_for_module,
244*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_scope_for_type_definition,
245*99e0aae7SDavid Rees      },
246*99e0aae7SDavid Rees      parameters={"errors": errors, "scope": symbol_tables})
247*99e0aae7SDavid Rees  return symbol_tables, errors
248*99e0aae7SDavid Rees
249*99e0aae7SDavid Rees
250*99e0aae7SDavid Reesdef _add_alias_to_scope(name_ir, table, scope, alias, visibility, errors):
251*99e0aae7SDavid Rees  """Adds the given name to the scope as an alias."""
252*99e0aae7SDavid Rees  name = name_ir.text
253*99e0aae7SDavid Rees  new_scope = _Scope(_nested_name(scope, name), name_ir.source_location,
254*99e0aae7SDavid Rees                     visibility, alias)
255*99e0aae7SDavid Rees  scoped_table = table[scope.module_file]
256*99e0aae7SDavid Rees  for path_element in scope.object_path:
257*99e0aae7SDavid Rees    scoped_table = scoped_table[path_element]
258*99e0aae7SDavid Rees  if name in scoped_table:
259*99e0aae7SDavid Rees    errors.append(duplicate_name_error(
260*99e0aae7SDavid Rees        scoped_table.canonical_name.module_file, name_ir.source_location, name,
261*99e0aae7SDavid Rees        FileLocation(scoped_table[name].canonical_name.module_file,
262*99e0aae7SDavid Rees                     scoped_table[name].source_location)))
263*99e0aae7SDavid Rees  else:
264*99e0aae7SDavid Rees    scoped_table[name] = new_scope
265*99e0aae7SDavid Rees  return new_scope
266*99e0aae7SDavid Rees
267*99e0aae7SDavid Rees
268*99e0aae7SDavid Reesdef _resolve_head_of_field_reference(field_reference, table, current_scope,
269*99e0aae7SDavid Rees                                     visible_scopes, source_file_name, errors):
270*99e0aae7SDavid Rees  return _resolve_reference(
271*99e0aae7SDavid Rees      field_reference.path[0], table, current_scope,
272*99e0aae7SDavid Rees      visible_scopes, source_file_name, errors)
273*99e0aae7SDavid Rees
274*99e0aae7SDavid Rees
275*99e0aae7SDavid Reesdef _resolve_reference(reference, table, current_scope, visible_scopes,
276*99e0aae7SDavid Rees                       source_file_name, errors):
277*99e0aae7SDavid Rees  """Sets the canonical name of the given reference."""
278*99e0aae7SDavid Rees  if reference.HasField("canonical_name"):
279*99e0aae7SDavid Rees    # This reference has already been resolved by the _resolve_field_reference
280*99e0aae7SDavid Rees    # pass.
281*99e0aae7SDavid Rees    return
282*99e0aae7SDavid Rees  target = _find_target_of_reference(reference, table, current_scope,
283*99e0aae7SDavid Rees                                     visible_scopes, source_file_name, errors)
284*99e0aae7SDavid Rees  if target is not None:
285*99e0aae7SDavid Rees    assert not target.alias
286*99e0aae7SDavid Rees    ir_data_utils.builder(reference).canonical_name.CopyFrom(target.canonical_name)
287*99e0aae7SDavid Rees
288*99e0aae7SDavid Rees
289*99e0aae7SDavid Reesdef _find_target_of_reference(reference, table, current_scope, visible_scopes,
290*99e0aae7SDavid Rees                              source_file_name, errors):
291*99e0aae7SDavid Rees  """Returns the resolved name of the given reference."""
292*99e0aae7SDavid Rees  found_in_table = None
293*99e0aae7SDavid Rees  name = reference.source_name[0].text
294*99e0aae7SDavid Rees  for scope in visible_scopes:
295*99e0aae7SDavid Rees    scoped_table = table[scope.module_file]
296*99e0aae7SDavid Rees    for path_element in scope.object_path or []:
297*99e0aae7SDavid Rees      scoped_table = scoped_table[path_element]
298*99e0aae7SDavid Rees    if (name in scoped_table and
299*99e0aae7SDavid Rees        (scope == current_scope or
300*99e0aae7SDavid Rees         scoped_table[name].visibility == _Scope.SEARCHABLE)):
301*99e0aae7SDavid Rees      # Prelude is "", so explicitly check for None.
302*99e0aae7SDavid Rees      if found_in_table is not None:
303*99e0aae7SDavid Rees        # TODO(bolms): Currently, this catches the case where a module tries to
304*99e0aae7SDavid Rees        # use a name that is defined (at the same scope) in two different
305*99e0aae7SDavid Rees        # modules.  It may make sense to raise duplicate_name_error whenever two
306*99e0aae7SDavid Rees        # modules define the same name (whether it is used or not), and reserve
307*99e0aae7SDavid Rees        # ambiguous_name_error for cases where a name is found in multiple
308*99e0aae7SDavid Rees        # scopes.
309*99e0aae7SDavid Rees        errors.append(ambiguous_name_error(
310*99e0aae7SDavid Rees            source_file_name, reference.source_location, name, [FileLocation(
311*99e0aae7SDavid Rees                found_in_table[name].canonical_name.module_file,
312*99e0aae7SDavid Rees                found_in_table[name].source_location), FileLocation(
313*99e0aae7SDavid Rees                    scoped_table[name].canonical_name.module_file, scoped_table[
314*99e0aae7SDavid Rees                        name].source_location)]))
315*99e0aae7SDavid Rees        continue
316*99e0aae7SDavid Rees      found_in_table = scoped_table
317*99e0aae7SDavid Rees      if reference.is_local_name:
318*99e0aae7SDavid Rees        # This is a little hacky.  When "is_local_name" is True, the name refers
319*99e0aae7SDavid Rees        # to a type that was defined inline.  In many cases, the type should be
320*99e0aae7SDavid Rees        # found at the same scope as the field; e.g.:
321*99e0aae7SDavid Rees        #
322*99e0aae7SDavid Rees        #     struct Foo:
323*99e0aae7SDavid Rees        #       0 [+1]  enum  bar:
324*99e0aae7SDavid Rees        #         BAZ = 1
325*99e0aae7SDavid Rees        #
326*99e0aae7SDavid Rees        # In this case, `Foo.bar` has type `Foo.Bar`.  Unfortunately, things
327*99e0aae7SDavid Rees        # break down a little bit when there is an inline type in an anonymous
328*99e0aae7SDavid Rees        # `bits`:
329*99e0aae7SDavid Rees        #
330*99e0aae7SDavid Rees        #     struct Foo:
331*99e0aae7SDavid Rees        #       0 [+1]  bits:
332*99e0aae7SDavid Rees        #         0 [+7]  enum  bar:
333*99e0aae7SDavid Rees        #           BAZ = 1
334*99e0aae7SDavid Rees        #
335*99e0aae7SDavid Rees        # Types inside of anonymous `bits` are hoisted into their parent type,
336*99e0aae7SDavid Rees        # so instead of `Foo.EmbossReservedAnonymous1.Bar`, `bar`'s type is just
337*99e0aae7SDavid Rees        # `Foo.Bar`.  Unfortunately, the field is still
338*99e0aae7SDavid Rees        # `Foo.EmbossReservedAnonymous1.bar`, so `bar`'s type won't be found in
339*99e0aae7SDavid Rees        # `bar`'s `current_scope`.
340*99e0aae7SDavid Rees        #
341*99e0aae7SDavid Rees        # (The name `bar` is exposed from `Foo` as an alias virtual field, so
342*99e0aae7SDavid Rees        # perhaps the correct answer is to allow type aliases, so that `Bar` can
343*99e0aae7SDavid Rees        # be found in both `Foo` and `Foo.EmbossReservedAnonymous1`.  That would
344*99e0aae7SDavid Rees        # involve an entirely new feature, though.)
345*99e0aae7SDavid Rees        #
346*99e0aae7SDavid Rees        # The workaround here is to search scopes from the innermost outward,
347*99e0aae7SDavid Rees        # and just stop as soon as a match is found.  This isn't ideal, because
348*99e0aae7SDavid Rees        # it relies on other bits of the front end having correctly added the
349*99e0aae7SDavid Rees        # inline type to the correct scope before symbol resolution, but it does
350*99e0aae7SDavid Rees        # work.  Names with False `is_local_name` will still be checked for
351*99e0aae7SDavid Rees        # ambiguity.
352*99e0aae7SDavid Rees        break
353*99e0aae7SDavid Rees  if found_in_table is None:
354*99e0aae7SDavid Rees    errors.append(missing_name_error(
355*99e0aae7SDavid Rees        source_file_name, reference.source_name[0].source_location, name))
356*99e0aae7SDavid Rees  if not errors:
357*99e0aae7SDavid Rees    for subname in reference.source_name:
358*99e0aae7SDavid Rees      if subname.text not in found_in_table:
359*99e0aae7SDavid Rees        errors.append(missing_name_error(source_file_name,
360*99e0aae7SDavid Rees                                         subname.source_location, subname.text))
361*99e0aae7SDavid Rees        return None
362*99e0aae7SDavid Rees      found_in_table = found_in_table[subname.text]
363*99e0aae7SDavid Rees      while found_in_table.alias:
364*99e0aae7SDavid Rees        referenced_table = table
365*99e0aae7SDavid Rees        for name in found_in_table.alias:
366*99e0aae7SDavid Rees          referenced_table = referenced_table[name]
367*99e0aae7SDavid Rees          # TODO(bolms): This section should really be a recursive lookup
368*99e0aae7SDavid Rees          # function, which would be able to handle arbitrary aliases through
369*99e0aae7SDavid Rees          # other aliases.
370*99e0aae7SDavid Rees          #
371*99e0aae7SDavid Rees          # This should be fine for now, since the only aliases here should be
372*99e0aae7SDavid Rees          # imports, which can't refer to other imports.
373*99e0aae7SDavid Rees          assert not referenced_table.alias, "Alias found to contain alias."
374*99e0aae7SDavid Rees        found_in_table = referenced_table
375*99e0aae7SDavid Rees    return found_in_table
376*99e0aae7SDavid Rees  return None
377*99e0aae7SDavid Rees
378*99e0aae7SDavid Rees
379*99e0aae7SDavid Reesdef _resolve_field_reference(field_reference, source_file_name, errors, ir):
380*99e0aae7SDavid Rees  """Resolves the References inside of a FieldReference."""
381*99e0aae7SDavid Rees  if field_reference.path[-1].HasField("canonical_name"):
382*99e0aae7SDavid Rees    # Already done.
383*99e0aae7SDavid Rees    return
384*99e0aae7SDavid Rees  previous_field = ir_util.find_object_or_none(field_reference.path[0], ir)
385*99e0aae7SDavid Rees  previous_reference = field_reference.path[0]
386*99e0aae7SDavid Rees  for ref in field_reference.path[1:]:
387*99e0aae7SDavid Rees    while ir_util.field_is_virtual(previous_field):
388*99e0aae7SDavid Rees      if (previous_field.read_transform.WhichOneof("expression") ==
389*99e0aae7SDavid Rees          "field_reference"):
390*99e0aae7SDavid Rees        # Pass a separate error list into the recursive _resolve_field_reference
391*99e0aae7SDavid Rees        # call so that only one copy of the error for a particular reference
392*99e0aae7SDavid Rees        # will actually surface: in particular, the one that results from a
393*99e0aae7SDavid Rees        # direct call from traverse_ir_top_down into _resolve_field_reference.
394*99e0aae7SDavid Rees        new_errors = []
395*99e0aae7SDavid Rees        _resolve_field_reference(
396*99e0aae7SDavid Rees            previous_field.read_transform.field_reference,
397*99e0aae7SDavid Rees            previous_field.name.canonical_name.module_file, new_errors, ir)
398*99e0aae7SDavid Rees        # If the recursive _resolve_field_reference was unable to resolve the
399*99e0aae7SDavid Rees        # field, then bail.  Otherwise we get a cascade of errors, where an
400*99e0aae7SDavid Rees        # error in `x` leads to errors in anything trying to reach a member of
401*99e0aae7SDavid Rees        # `x`.
402*99e0aae7SDavid Rees        if not previous_field.read_transform.field_reference.path[-1].HasField(
403*99e0aae7SDavid Rees            "canonical_name"):
404*99e0aae7SDavid Rees          return
405*99e0aae7SDavid Rees        previous_field = ir_util.find_object(
406*99e0aae7SDavid Rees            previous_field.read_transform.field_reference.path[-1], ir)
407*99e0aae7SDavid Rees      else:
408*99e0aae7SDavid Rees        errors.append(
409*99e0aae7SDavid Rees            noncomposite_subfield_error(source_file_name,
410*99e0aae7SDavid Rees                                        previous_reference.source_location,
411*99e0aae7SDavid Rees                                        previous_reference.source_name[0].text))
412*99e0aae7SDavid Rees        return
413*99e0aae7SDavid Rees    if previous_field.type.WhichOneof("type") == "array_type":
414*99e0aae7SDavid Rees      errors.append(
415*99e0aae7SDavid Rees          array_subfield_error(source_file_name,
416*99e0aae7SDavid Rees                               previous_reference.source_location,
417*99e0aae7SDavid Rees                               previous_reference.source_name[0].text))
418*99e0aae7SDavid Rees      return
419*99e0aae7SDavid Rees    assert previous_field.type.WhichOneof("type") == "atomic_type"
420*99e0aae7SDavid Rees    member_name = ir_data_utils.copy(
421*99e0aae7SDavid Rees        previous_field.type.atomic_type.reference.canonical_name)
422*99e0aae7SDavid Rees    ir_data_utils.builder(member_name).object_path.extend([ref.source_name[0].text])
423*99e0aae7SDavid Rees    previous_field = ir_util.find_object_or_none(member_name, ir)
424*99e0aae7SDavid Rees    if previous_field is None:
425*99e0aae7SDavid Rees      errors.append(
426*99e0aae7SDavid Rees          missing_name_error(source_file_name,
427*99e0aae7SDavid Rees                             ref.source_name[0].source_location,
428*99e0aae7SDavid Rees                             ref.source_name[0].text))
429*99e0aae7SDavid Rees      return
430*99e0aae7SDavid Rees    ir_data_utils.builder(ref).canonical_name.CopyFrom(member_name)
431*99e0aae7SDavid Rees    previous_reference = ref
432*99e0aae7SDavid Rees
433*99e0aae7SDavid Rees
434*99e0aae7SDavid Reesdef _set_visible_scopes_for_type_definition(type_definition, visible_scopes):
435*99e0aae7SDavid Rees  """Sets current_scope and visible_scopes for the given type_definition."""
436*99e0aae7SDavid Rees  return {
437*99e0aae7SDavid Rees      "current_scope": type_definition.name.canonical_name,
438*99e0aae7SDavid Rees
439*99e0aae7SDavid Rees      # In order to ensure that the iteration through scopes in
440*99e0aae7SDavid Rees      # _find_target_of_reference will go from innermost to outermost, it is
441*99e0aae7SDavid Rees      # important that the current scope (type_definition.name.canonical_name)
442*99e0aae7SDavid Rees      # precedes the previous visible_scopes here.
443*99e0aae7SDavid Rees      "visible_scopes": (type_definition.name.canonical_name,) + visible_scopes,
444*99e0aae7SDavid Rees  }
445*99e0aae7SDavid Rees
446*99e0aae7SDavid Rees
447*99e0aae7SDavid Reesdef _set_visible_scopes_for_module(module):
448*99e0aae7SDavid Rees  """Sets visible_scopes for the given module."""
449*99e0aae7SDavid Rees  self_scope = ir_data.CanonicalName(module_file=module.source_file_name)
450*99e0aae7SDavid Rees  extra_visible_scopes = []
451*99e0aae7SDavid Rees  for foreign_import in module.foreign_import:
452*99e0aae7SDavid Rees    # Anonymous imports are searched for top-level names; named imports are not.
453*99e0aae7SDavid Rees    # As of right now, only the prelude should be imported anonymously; other
454*99e0aae7SDavid Rees    # modules must be imported with names.
455*99e0aae7SDavid Rees    if not foreign_import.local_name.text:
456*99e0aae7SDavid Rees      extra_visible_scopes.append(
457*99e0aae7SDavid Rees          ir_data.CanonicalName(module_file=foreign_import.file_name.text))
458*99e0aae7SDavid Rees  return {"visible_scopes": (self_scope,) + tuple(extra_visible_scopes)}
459*99e0aae7SDavid Rees
460*99e0aae7SDavid Rees
461*99e0aae7SDavid Reesdef _set_visible_scopes_for_attribute(attribute, field, visible_scopes):
462*99e0aae7SDavid Rees  """Sets current_scope and visible_scopes for the attribute."""
463*99e0aae7SDavid Rees  del attribute  # Unused
464*99e0aae7SDavid Rees  if field is None:
465*99e0aae7SDavid Rees    return
466*99e0aae7SDavid Rees  return {
467*99e0aae7SDavid Rees      "current_scope": field.name.canonical_name,
468*99e0aae7SDavid Rees      "visible_scopes": (field.name.canonical_name,) + visible_scopes,
469*99e0aae7SDavid Rees  }
470*99e0aae7SDavid Rees
471*99e0aae7SDavid Reesdef _module_source_from_table_action(m, table):
472*99e0aae7SDavid Rees  return {"module": table[m.source_file_name]}
473*99e0aae7SDavid Rees
474*99e0aae7SDavid Reesdef _resolve_symbols_from_table(ir, table):
475*99e0aae7SDavid Rees  """Resolves all references in the given IR, given the constructed table."""
476*99e0aae7SDavid Rees  errors = []
477*99e0aae7SDavid Rees  # Symbol resolution is broken into five passes.  First, this code resolves any
478*99e0aae7SDavid Rees  # imports, and adds import aliases to modules.
479*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
480*99e0aae7SDavid Rees      ir, [ir_data.Import], _add_import_to_scope,
481*99e0aae7SDavid Rees      incidental_actions={
482*99e0aae7SDavid Rees          ir_data.Module: _module_source_from_table_action,
483*99e0aae7SDavid Rees      },
484*99e0aae7SDavid Rees      parameters={"errors": errors, "table": table})
485*99e0aae7SDavid Rees  if errors:
486*99e0aae7SDavid Rees    return errors
487*99e0aae7SDavid Rees  # Next, this resolves all absolute references (e.g., it resolves "UInt" in
488*99e0aae7SDavid Rees  # "0:1  UInt  field" to [prelude]::UInt).
489*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
490*99e0aae7SDavid Rees      ir, [ir_data.Reference], _resolve_reference,
491*99e0aae7SDavid Rees      skip_descendants_of=(ir_data.FieldReference,),
492*99e0aae7SDavid Rees      incidental_actions={
493*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_visible_scopes_for_type_definition,
494*99e0aae7SDavid Rees          ir_data.Module: _set_visible_scopes_for_module,
495*99e0aae7SDavid Rees          ir_data.Attribute: _set_visible_scopes_for_attribute,
496*99e0aae7SDavid Rees      },
497*99e0aae7SDavid Rees      parameters={"table": table, "errors": errors, "field": None})
498*99e0aae7SDavid Rees  # Lastly, head References to fields (e.g., the `a` of `a.b.c`) are resolved.
499*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
500*99e0aae7SDavid Rees      ir, [ir_data.FieldReference], _resolve_head_of_field_reference,
501*99e0aae7SDavid Rees      incidental_actions={
502*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_visible_scopes_for_type_definition,
503*99e0aae7SDavid Rees          ir_data.Module: _set_visible_scopes_for_module,
504*99e0aae7SDavid Rees          ir_data.Attribute: _set_visible_scopes_for_attribute,
505*99e0aae7SDavid Rees      },
506*99e0aae7SDavid Rees      parameters={"table": table, "errors": errors, "field": None})
507*99e0aae7SDavid Rees  return errors
508*99e0aae7SDavid Rees
509*99e0aae7SDavid Rees
510*99e0aae7SDavid Reesdef resolve_field_references(ir):
511*99e0aae7SDavid Rees  """Resolves structure member accesses ("field.subfield") in ir."""
512*99e0aae7SDavid Rees  errors = []
513*99e0aae7SDavid Rees  traverse_ir.fast_traverse_ir_top_down(
514*99e0aae7SDavid Rees      ir, [ir_data.FieldReference], _resolve_field_reference,
515*99e0aae7SDavid Rees      incidental_actions={
516*99e0aae7SDavid Rees          ir_data.TypeDefinition: _set_visible_scopes_for_type_definition,
517*99e0aae7SDavid Rees          ir_data.Module: _set_visible_scopes_for_module,
518*99e0aae7SDavid Rees          ir_data.Attribute: _set_visible_scopes_for_attribute,
519*99e0aae7SDavid Rees      },
520*99e0aae7SDavid Rees      parameters={"errors": errors, "field": None})
521*99e0aae7SDavid Rees  return errors
522*99e0aae7SDavid Rees
523*99e0aae7SDavid Rees
524*99e0aae7SDavid Reesdef resolve_symbols(ir):
525*99e0aae7SDavid Rees  """Resolves the symbols in all modules in ir."""
526*99e0aae7SDavid Rees  symbol_tables, errors = _construct_symbol_tables(ir)
527*99e0aae7SDavid Rees  if errors:
528*99e0aae7SDavid Rees    return errors
529*99e0aae7SDavid Rees  return _resolve_symbols_from_table(ir, symbol_tables)
530