//===-- tools/extra/clang-reorder-fields/utils/Designator.h -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the declarations of the Designator and Designators
/// utility classes.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H

#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"

namespace clang {
namespace reorder_fields {

/// Represents a part of a designation in a C99/C++20 designated initializer. It
/// is a tagged union of different kinds of designators: struct, array and array
/// range. Holds enough information to be able to advance to the next field and
/// to know when all fields have been iterated through.
class Designator {
public:
  enum Kind { STRUCT, ARRAY, ARRAY_RANGE };

  Designator(const QualType Type, RecordDecl::field_iterator Field,
             const RecordDecl *RD)
      : Tag(STRUCT), Type(Type), StructIt({Field, RD}) {}

  Designator(const QualType Type, uint64_t Idx, uint64_t Size)
      : Tag(ARRAY), Type(Type), ArrayIt({Idx, Size}) {}

  Designator(const QualType Type, uint64_t Start, uint64_t End, uint64_t Size)
      : Tag(ARRAY_RANGE), Type(Type), ArrayRangeIt({Start, End, Size}) {}

  /// Moves the iterator to the next element.
  void advanceToNextField();

  /// Checks if the iterator has iterated through all elements.
  bool isFinished();

  Kind getTag() const { return Tag; }
  QualType getType() const { return Type; }

  const RecordDecl::field_iterator getStructIter() const {
    assert(Tag == STRUCT && "Must be a field designator");
    return StructIt.Field;
  }

  const RecordDecl *getStructDecl() const {
    assert(Tag == STRUCT && "Must be a field designator");
    return StructIt.Record;
  }

  uint64_t getArrayIndex() const {
    assert(Tag == ARRAY && "Must be an array designator");
    return ArrayIt.Index;
  }

  uint64_t getArrayRangeStart() const {
    assert(Tag == ARRAY_RANGE && "Must be an array range designator");
    return ArrayRangeIt.Start;
  }

  uint64_t getArrayRangeEnd() const {
    assert(Tag == ARRAY_RANGE && "Must be an array range designator");
    return ArrayRangeIt.End;
  }

  uint64_t getArraySize() const {
    assert((Tag == ARRAY || Tag == ARRAY_RANGE) &&
           "Must be an array or range designator");
    if (Tag == ARRAY)
      return ArrayIt.Size;
    return ArrayRangeIt.Size;
  }

private:
  /// Type of the designator.
  Kind Tag;

  /// Type of the designated entry. For arrays this is the type of the element.
  QualType Type;

  /// Field designator has the iterator to the field and the record the field
  /// is declared in.
  struct StructIter {
    RecordDecl::field_iterator Field;
    const RecordDecl *Record;
  };

  /// Array designator has an index and size of the array.
  struct ArrayIter {
    uint64_t Index;
    uint64_t Size;
  };

  /// Array range designator has a start and end index and size of the array.
  struct ArrayRangeIter {
    uint64_t Start;
    uint64_t End;
    uint64_t Size;
  };

  union {
    StructIter StructIt;
    ArrayIter ArrayIt;
    ArrayRangeIter ArrayRangeIt;
  };
};

/// List of designators.
class Designators {
public:
  /// Initialize to the first member of the struct/array. Enters implicit
  /// initializer lists until a type that matches Init is found.
  Designators(const Expr *Init, const InitListExpr *ILE,
              const ASTContext *Context);

  /// Initialize to the designators of the given expression.
  Designators(const DesignatedInitExpr *DIE, const InitListExpr *ILE,
              const ASTContext *Context);

  /// Return whether this designator list is valid.
  bool isValid() const { return !DesignatorList.empty(); }

  /// Moves the designators to the next initializer in the struct/array. If the
  /// type of next initializer doesn't match the expected type then there are
  /// omitted braces and we add new designators to reflect that.
  bool advanceToNextField(const Expr *Init);

  /// Gets a string representation from a list of designators. This string will
  /// be inserted before an initializer expression to make it designated.
  std::string toString() const;

  size_t size() const { return DesignatorList.size(); }

  SmallVector<Designator>::const_iterator begin() const {
    return DesignatorList.begin();
  }
  SmallVector<Designator>::const_iterator end() const {
    return DesignatorList.end();
  }

private:
  /// Enters any implicit initializer lists until a type that matches the given
  /// expression is found.
  bool enterImplicitInitLists(const Expr *Init);

  const ASTContext *Context;
  SmallVector<Designator, 1> DesignatorList;
};

} // namespace reorder_fields
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H
