// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_TOOLCHAIN_CHECK_DECLARATION_NAME_STACK_H_
#define CARBON_TOOLCHAIN_CHECK_DECLARATION_NAME_STACK_H_

#include "llvm/ADT/SmallVector.h"
#include "toolchain/parse/tree.h"
#include "toolchain/sem_ir/node.h"

namespace Carbon::Check {

class Context;

// Provides support and stacking for qualified declaration name handling.
//
// A qualified declaration name will consist of entries which are either
// Identifiers or full expressions. Expressions are expected to resolve to
// types, such as how `fn Vector(i32).Clear() { ... }` uses the expression
// `Vector(i32)` to indicate the type whose member is being declared.
// Identifiers such as `Clear` will be resolved to a name if possible, for
// example when declaring things that are in a non-generic type or namespace,
// and are otherwise marked as an unresolved identifier.
//
// Unresolved identifiers are valid if and only if they are the last step of a
// qualified name; all resolved qualifiers must resolve to an entity with
// members, such as a namespace. Resolved identifiers in the last step will
// occur for both out-of-line definitions and new declarations, depending on
// context.
//
// Example state transitions:
//
// ```
// // New -> Unresolved, because `MyNamespace` is newly declared.
// namespace MyNamespace;
//
// // New -> Resolved -> Unresolved, because `MyType` is newly declared.
// class MyNamespace.MyType;
//
// // New -> Resolved -> Resolved, because `MyType` was forward declared.
// class MyNamespace.MyType {
//   // New -> Unresolved, because `DoSomething` is newly declared.
//   fn DoSomething();
// }
//
// // New -> Resolved -> Resolved -> ResolvedNonScope, because `DoSomething`
// // is forward declared in `MyType`, but is not a scope itself.
// fn MyNamespace.MyType.DoSomething() { ... }
// ```
class DeclarationNameStack {
 public:
  // Context for declaration name construction.
  struct NameContext {
    enum class State : int8_t {
      // A new context which has not processed any parts of the qualifier.
      New,

      // A node ID has been resolved, whether through an identifier or
      // expression. This provided a new scope, such as a type.
      Resolved,

      // A node ID has been resolved, whether through an identifier or
      // expression. It did not provide a new scope, so must be the final part,
      // such as an out-of-line function definition.
      ResolvedNonScope,

      // An identifier didn't resolve.
      Unresolved,

      // An error has occurred, such as an additional qualifier past an
      // unresolved name. No new diagnostics should be emitted.
      Error,
    };

    State state = State::New;

    // The scope which qualified names are added to. For unqualified names,
    // this will be Invalid to indicate the current scope should be used.
    SemIR::NameScopeId target_scope_id = SemIR::NameScopeId::Invalid;

    // The last parse node used.
    Parse::Node parse_node = Parse::Node::Invalid;

    union {
      // The ID of a resolved qualifier, including both identifiers and
      // expressions. Invalid indicates resolution failed.
      SemIR::NodeId resolved_node_id = SemIR::NodeId::Invalid;

      // The ID of an unresolved identifier.
      SemIR::StringId unresolved_name_id;
    };
  };

  explicit DeclarationNameStack(Context* context) : context_(context) {}

  // Pushes processing of a new declaration name, which will be used
  // contextually.
  auto Push() -> void;

  // Pops the current declaration name processing, returning the final context
  // for adding the name to lookup. This also pops the final name node from the
  // node stack, which will be applied to the declaration name if appropriate.
  auto Pop() -> NameContext;

  // Applies an expression from the node stack to the top of the declaration
  // name stack.
  auto ApplyExpressionQualifier(Parse::Node parse_node, SemIR::NodeId node_id)
      -> void;

  // Applies a Name from the node stack to the top of the declaration name
  // stack.
  auto ApplyNameQualifier(Parse::Node parse_node, SemIR::StringId name_id)
      -> void;

  // Adds a name to name lookup. Prints a diagnostic for name conflicts.
  auto AddNameToLookup(NameContext name_context, SemIR::NodeId target_id)
      -> void;

 private:
  // Returns true if the context is in a state where it can resolve qualifiers.
  // Updates name_context as needed.
  auto CanResolveQualifier(NameContext& name_context, Parse::Node parse_node)
      -> bool;

  // Updates the scope on name_context as needed. This is called after
  // resolution is complete, whether for Name or expression.
  auto UpdateScopeIfNeeded(NameContext& name_context) -> void;

  // The linked context.
  Context* context_;

  // Provides nesting for construction.
  llvm::SmallVector<NameContext> declaration_name_stack_;
};

}  // namespace Carbon::Check

#endif  // CARBON_TOOLCHAIN_CHECK_DECLARATION_NAME_STACK_H_
