text: # Copyright 2021 The Blqs Developers
  #
  # 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.
  import inspect
  
  from typing import Any, Dict, Optional
  
  
  class GeneratedCodeException(Exception):
      def __init__(
          self,
          linenos_dict: Dict[int, int],
          original_filename: Optional[str],
          generated_filename: str,
      ):
          """An exception raised when blqs generated code throws an exception.
  
          This exception allows for a description of where the original exception occurred when
          the exception is thrown from generated code.
  
          Args:
              linenos_dict: A map from the line numbers in the traceback to the
                  original lines in the code that generated this code.
              original_filename: The filename of the code that generated the code that threw the
                  exception. Can be None if this could not be determined.
              generated_filename: The filename of the generated code that threw the exception.
          """
          self._linenos_dict = linenos_dict
          self._original_filename = original_filename
          self._generated_filename = generated_filename
  
      def __str__(self):
          filename_str = self._original_filename or "<could not be determined>"
          lineno_strs = [str(k) + " -> " + str(v) for k, v in self._linenos_dict.items()]
          linemap_str = (
              "\n".join(lineno_strs) if len(lineno_strs) > 0 else "<could not be determined>"
          )
  
          return (
              "Exception in code generated by blqs.\n\nIn the trace below to find the generated "
              f"code, map the filename according to \n\t{self._generated_filename} -> {filename_str}"
              f"\nand map the line numbers for that file according to\n\t{linemap_str}"
          )
  
      def linenos_dict(self) -> Dict[int, int]:
          return self._linenos_dict
  
      def original_filename(self) -> Optional[str]:
          return self._original_filename
  
      def generated_filename(self) -> str:
          return self._generated_filename
  
  
  def _raise_with_line_mapping(
      e: Exception, obj: Any, line_map: Dict[int, int], generated_filename: str
  ):
      """Raise the given exception with information about the original location of the exception.
  
      If an exception is raised in generated code, this will add a cause that describes the
      original location of the code that produced the generated code. This is a best effort, if
      the traceback is missing or does not have a line number, or inspect cannot determine the
      filename and line number of the original object, this will still add the cause, but without
      this information.
  
      Args:
          e: The exception to possibly add a cause for.
          obj: The original object from which code was generated. This is used to calculate offsets
              for line numbers and filenames.
          line_map: Map from the line number in the generated code to the line number in the
              original code that generated the code.
          generated_filename: The name of the file where the generated code lives.
  
      Raises:
          e: The original exception. If it can be determined this exception has a cause of
              a `GeneratedCodeException` that contains information about the original file and
              line number of the code that generated the code that threw the exception.
      """
      offset_lineno = inspect.getsourcelines(obj)[-1]
      original_filename = inspect.getsourcefile(obj)
      last_tb = getattr(e, "__traceback__", None)
      linenos = []
      while last_tb is not None:
          if (
              getattr(last_tb, "tb_frame", None) is not None
              and getattr(last_tb.tb_frame, "f_code", None) is not None
          ):
              tb_filename = getattr(last_tb.tb_frame.f_code, "co_filename", None)
              if tb_filename == generated_filename:
                  linenos.append(last_tb.tb_lineno)
          last_tb = last_tb.tb_next
  
      linenos_dict = {}
      for lineno in linenos:
          if lineno in line_map and offset_lineno is not None:
              # -1 from adding two indices that start at 1, not 0.
              linenos_dict[lineno] = line_map[lineno] + offset_lineno - 1
      cause = GeneratedCodeException(linenos_dict, original_filename, generated_filename)
      raise e from cause
  