docs: def check_edge(
      state,
      name,
      index=0,
      missing_msg="Check the {ast_path}. Could not find the {index}{field_name}.",
  ):
      """Select an attribute from an abstract syntax tree (AST) node, using the attribute name.
  
      Args:
          state: State instance describing student and solution code. Can be omitted if used with Ex().
          name: the name of the attribute to select from current AST node.
          index: entry to get from a list field. If too few entires, will fail with missing_msg.
          missing_msg: feedback message if attribute is not in student AST.
  
      :Example:
          If both the student and solution code are.. ::
  
              SELECT a FROM b; SELECT x FROM y;
  
          then we can get the from_clause using ::
  
              # approach 1: with manually created State instance -----
              state = State(*args, **kwargs)
              select = check_node(state, 'SelectStmt', 0)
              clause = check_edge(select, 'from_clause')
  
              # approach 2: with Ex and chaining ---------------------
              select = Ex().check_node('SelectStmt', 0)           # get first select statement
              clause =  select.check_edge('from_clause', None)    # get from_clause (a list)
              clause2 = select.check_edge('from_clause', 0)       # get first entry in from_clause
      """
      try:
          sol_attr = getattr(state.solution_ast, name)
          if sol_attr and isinstance(sol_attr, list) and index is not None:
              sol_attr = sol_attr[index]
      except IndexError:
          raise IndexError("Can't get %s attribute" % name)
  
      # use speaker on ast dialect module to get message, or fall back to generic
      ast_path = state.get_ast_path() or "highlighted code"
      _msg = state.ast_dispatcher.describe(
          state.student_ast, missing_msg, field=name, index=index, ast_path=ast_path
      )
      if _msg is None:
          _msg = MSG_CHECK_FALLBACK
  
      try:
          stu_attr = getattr(state.student_ast, name)
          if stu_attr and isinstance(stu_attr, list) and index is not None:
              stu_attr = stu_attr[index]
      except:
          state.report(Feedback(_msg))
  
      # fail if attribute exists, but is none only for student
      if stu_attr is None and sol_attr is not None:
          state.report(Feedback(_msg))
  
      action = {"type": "check_edge", "kwargs": {"name": name, "index": index}}
  
      return state.to_child(
          student_ast=stu_attr, solution_ast=sol_attr, history=state.history + (action,)
      )