Repository: carbon-language/carbon-lang
File: toolchain/check/handle_class.cpp (C++)

Code before:
  LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Extend);
  if (!introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
    CARBON_DIAGNOSTIC(BaseMissingExtend, Error,
                      "missing `extend` before `base` declaration");
    context.emitter().Emit(node_id, BaseMissingExtend);
  }

  auto parent_class_decl =
      GetCurrentScopeAsClassOrDiagnose(context, node_id, Lex::TokenKind::Base);
  if (!parent_class_decl) {
    return true;
  }

  auto& class_info = context.classes().Get(parent_class_decl->class_id);
  if (class_info.base_id.is_valid()) {
    DiagnoseClassSpecificDeclRepeated(context, node_id, class_info.base_id,
                                      Lex::TokenKind::Base);
    return true;
  }

  if (!context.struct_type_fields_stack().PeekArray().empty()) {
    CARBON_DIAGNOSTIC(BaseDeclAfterFieldDecl, Error,
                      "`{0}` declaration appears after field declaration(s)",
                      Lex::TokenKind);
    context.emitter().Emit(node_id, BaseDeclAfterFieldDecl,
                           Lex::TokenKind::Base);
    return true;
  }

  auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id);

  // The `base` value in the class scope has an unbound element type. Instance
  // binding will be performed when it's found by name lookup into an instance.
  auto field_type_id =
      context.GetUnboundElementType(class_info.self_type_id, base_info.type_id);
  class_info.base_id = context.AddInst<SemIR::BaseDecl>(
      node_id, {.type_id = field_type_id,
                .base_type_id = base_info.type_id,
                .index = SemIR::ElementIndex(
                    context.struct_type_fields_stack().PeekArray().size())});

  if (base_info.type_id != SemIR::TypeId::Error) {
    auto base_class_info = context.classes().Get(
        context.types().GetAs<SemIR::ClassType>(base_info.type_id).class_id);
    class_info.is_dynamic |= base_class_info.is_dynamic;
  }

  // Add a corresponding field to the object representation of the class.
  // TODO: Consider whether we want to use `partial T` here.
  // TODO: Should we diagnose if there are already any fields?
  context.struct_type_fields_stack().AppendToTop(

Review comment (suggestion):
Does it work to add a note for the first field location? i.e., something like:

```suggestion
    CARBON_DIAGNOSTIC(FirstFieldDecl, Note,
                      "First field declared here");
    context.emitter().Build(node_id, BaseDeclAfterFieldDecl,
                           Lex::TokenKind::Base).Note(fields.front(), FirstFieldDecl).Emit();
```

(I think `FirstFieldDecl` is a bad name, but I'm struggling with better naming)

Code after:
  LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Extend);
  if (!introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
    CARBON_DIAGNOSTIC(BaseMissingExtend, Error,
                      "missing `extend` before `base` declaration");
    context.emitter().Emit(node_id, BaseMissingExtend);
  }

  auto parent_class_decl =
      GetCurrentScopeAsClassOrDiagnose(context, node_id, Lex::TokenKind::Base);
  if (!parent_class_decl) {
    return true;
  }

  auto& class_info = context.classes().Get(parent_class_decl->class_id);
  if (class_info.base_id.is_valid()) {
    DiagnoseClassSpecificDeclRepeated(context, node_id, class_info.base_id,
                                      Lex::TokenKind::Base);
    return true;
  }

  if (!context.struct_type_fields_stack().PeekArray().empty()) {
    // TODO: Add note that includes the first field location as an example.
    CARBON_DIAGNOSTIC(
        BaseDeclAfterFieldDecl, Error,
        "`base` declaration must appear before field declarations");
    context.emitter().Emit(node_id, BaseDeclAfterFieldDecl);
    return true;
  }

  auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id);

  // The `base` value in the class scope has an unbound element type. Instance
  // binding will be performed when it's found by name lookup into an instance.
  auto field_type_id =
      context.GetUnboundElementType(class_info.self_type_id, base_info.type_id);
  class_info.base_id = context.AddInst<SemIR::BaseDecl>(
      node_id, {.type_id = field_type_id,
                .base_type_id = base_info.type_id,
                .index = SemIR::ElementIndex(
                    context.struct_type_fields_stack().PeekArray().size())});

  if (base_info.type_id != SemIR::TypeId::Error) {
    auto base_class_info = context.classes().Get(
        context.types().GetAs<SemIR::ClassType>(base_info.type_id).class_id);
    class_info.is_dynamic |= base_class_info.is_dynamic;
  }

  // Add a corresponding field to the object representation of the class.
  // TODO: Consider whether we want to use `partial T` here.
  // TODO: Should we diagnose if there are already any fields?
  context.struct_type_fields_stack().AppendToTop(