Forráskód Böngészése

Add diagnostic support so that we can just pass in TypeId. (#3695)

Note we may also want to do this with NameId, maybe some other things,
but the TypeId use is pretty broad and repetitive -- I thought I'd start
with it first.
Jon Ross-Perkins 2 éve
szülő
commit
1bf4dc53d9

+ 13 - 0
toolchain/check/check.cpp

@@ -74,6 +74,19 @@ class SemIRLocationTranslator
     }
   }
 
+  auto TranslateArg(DiagnosticTypeTranslation translation, llvm::Any arg) const
+      -> llvm::Any override {
+    switch (translation) {
+      case DiagnosticTypeTranslation::TypeId: {
+        auto type_id = llvm::any_cast<SemIR::TypeId>(arg);
+        return sem_ir_->StringifyType(type_id);
+      }
+      default:
+        return DiagnosticLocationTranslator<SemIRLocation>::TranslateArg(
+            translation, arg);
+    }
+  }
+
  private:
   auto GetLocationInFile(const SemIR::File* sem_ir,
                          Parse::NodeLocation node_location) const

+ 20 - 22
toolchain/check/convert.cpp

@@ -485,11 +485,10 @@ static auto ConvertStructToStructOrClass(Context& context,
           CARBON_DIAGNOSTIC(StructInitMissingFieldInConversion, Error,
                             "Cannot convert from struct type `{0}` to `{1}`: "
                             "missing field `{2}` in source type.",
-                            std::string, std::string, std::string);
+                            SemIR::TypeId, SemIR::TypeId, std::string);
           context.emitter().Emit(
               value_parse_node, StructInitMissingFieldInConversion,
-              sem_ir.StringifyType(value.type_id()),
-              sem_ir.StringifyType(target.type_id),
+              value.type_id(), target.type_id,
               sem_ir.names().GetFormatted(dest_field.name_id).str());
         }
         return SemIR::InstId::BuiltinError;
@@ -554,9 +553,9 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
     CARBON_DIAGNOSTIC(ConstructionOfAbstractClass, Error,
                       "Cannot construct instance of abstract class. "
                       "Consider using `partial {0}` instead.",
-                      std::string);
+                      SemIR::TypeId);
     context.emitter().Emit(value_id, ConstructionOfAbstractClass,
-                           context.sem_ir().StringifyType(target.type_id));
+                           target.type_id);
     return SemIR::InstId::BuiltinError;
   }
   if (class_info.object_repr_id == SemIR::TypeId::Error) {
@@ -865,9 +864,8 @@ static auto PerformCopy(Context& context, SemIR::InstId expr_id)
   // TODO: We don't yet have rules for whether and when a class type is
   // copyable, or how to perform the copy.
   CARBON_DIAGNOSTIC(CopyOfUncopyableType, Error,
-                    "Cannot copy value of type `{0}`.", std::string);
-  context.emitter().Emit(expr_id, CopyOfUncopyableType,
-                         context.sem_ir().StringifyType(type_id));
+                    "Cannot copy value of type `{0}`.", SemIR::TypeId);
+  context.emitter().Emit(expr_id, CopyOfUncopyableType, type_id);
   return SemIR::InstId::BuiltinError;
 }
 
@@ -897,19 +895,20 @@ auto Convert(Context& context, Parse::NodeId parse_node, SemIR::InstId expr_id,
   if (!context.TryToCompleteType(target.type_id, [&] {
         CARBON_DIAGNOSTIC(IncompleteTypeInInit, Error,
                           "Initialization of incomplete type `{0}`.",
-                          std::string);
+                          SemIR::TypeId);
         CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
                           "Forming value of incomplete type `{0}`.",
-                          std::string);
+                          SemIR::TypeId);
         CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
-                          "Invalid use of incomplete type `{0}`.", std::string);
-        return context.emitter().Build(
-            parse_node,
-            target.is_initializer() ? IncompleteTypeInInit
-            : target.kind == ConversionTarget::Value
-                ? IncompleteTypeInValueConversion
-                : IncompleteTypeInConversion,
-            context.sem_ir().StringifyType(target.type_id));
+                          "Invalid use of incomplete type `{0}`.",
+                          SemIR::TypeId);
+        return context.emitter().Build(parse_node,
+                                       target.is_initializer()
+                                           ? IncompleteTypeInInit
+                                       : target.kind == ConversionTarget::Value
+                                           ? IncompleteTypeInValueConversion
+                                           : IncompleteTypeInConversion,
+                                       target.type_id);
       })) {
     return SemIR::InstId::BuiltinError;
   }
@@ -927,17 +926,16 @@ auto Convert(Context& context, Parse::NodeId parse_node, SemIR::InstId expr_id,
   if (expr.type_id() != target.type_id) {
     CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error,
                       "Cannot implicitly convert from `{0}` to `{1}`.",
-                      std::string, std::string);
+                      SemIR::TypeId, SemIR::TypeId);
     CARBON_DIAGNOSTIC(ExplicitAsConversionFailure, Error,
                       "Cannot convert from `{0}` to `{1}` with `as`.",
-                      std::string, std::string);
+                      SemIR::TypeId, SemIR::TypeId);
     context.emitter()
         .Build(parse_node,
                target.kind == ConversionTarget::ExplicitAs
                    ? ExplicitAsConversionFailure
                    : ImplicitAsConversionFailure,
-               sem_ir.StringifyType(expr.type_id()),
-               sem_ir.StringifyType(target.type_id))
+               expr.type_id(), target.type_id)
         .Emit();
     return SemIR::InstId::BuiltinError;
   }

+ 2 - 3
toolchain/check/decl_name_stack.cpp

@@ -231,11 +231,10 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
                                 .TryAs<SemIR::ClassDecl>()) {
         CARBON_DIAGNOSTIC(QualifiedDeclInIncompleteClassScope, Error,
                           "Cannot declare a member of incomplete class `{0}`.",
-                          std::string);
+                          SemIR::TypeId);
         auto builder = context_->emitter().Build(
             name_context.parse_node, QualifiedDeclInIncompleteClassScope,
-            context_->sem_ir().StringifyType(
-                context_->classes().Get(class_decl->class_id).self_type_id));
+            context_->classes().Get(class_decl->class_id).self_type_id);
         context_->NoteIncompleteClass(class_decl->class_id, builder);
         builder.Emit();
       } else {

+ 4 - 5
toolchain/check/eval.cpp

@@ -257,11 +257,10 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
           context.ints().Get(bound->int_id).ule(index_val.getZExtValue())) {
         CARBON_DIAGNOSTIC(ArrayIndexOutOfBounds, Error,
                           "Array index `{0}` is past the end of type `{1}`.",
-                          llvm::APSInt, std::string);
-        context.emitter().Emit(
-            index_inst.index_id, ArrayIndexOutOfBounds,
-            llvm::APSInt(index_val, /*isUnsigned=*/true),
-            context.sem_ir().StringifyType(aggregate_type_id));
+                          llvm::APSInt, SemIR::TypeId);
+        context.emitter().Emit(index_inst.index_id, ArrayIndexOutOfBounds,
+                               llvm::APSInt(index_val, /*isUnsigned=*/true),
+                               aggregate_type_id);
         return SemIR::ConstantId::Error;
       }
     }

+ 9 - 10
toolchain/check/handle_binding_pattern.cpp

@@ -70,12 +70,12 @@ auto HandleAnyBindingPattern(Context& context, Parse::NodeId parse_node,
       cast_type_id = context.AsCompleteType(cast_type_id, [&] {
         CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
                           "{0} has incomplete type `{1}`.", llvm::StringLiteral,
-                          std::string);
-        return context.emitter().Build(
-            type_node, IncompleteTypeInVarDecl,
-            enclosing_class_decl ? llvm::StringLiteral("Field")
-                                 : llvm::StringLiteral("Variable"),
-            context.sem_ir().StringifyType(cast_type_id));
+                          SemIR::TypeId);
+        return context.emitter().Build(type_node, IncompleteTypeInVarDecl,
+                                       enclosing_class_decl
+                                           ? llvm::StringLiteral("Field")
+                                           : llvm::StringLiteral("Variable"),
+                                       cast_type_id);
       });
       if (enclosing_class_decl) {
         CARBON_CHECK(context_parse_node_kind ==
@@ -142,10 +142,9 @@ auto HandleAnyBindingPattern(Context& context, Parse::NodeId parse_node,
       cast_type_id = context.AsCompleteType(cast_type_id, [&] {
         CARBON_DIAGNOSTIC(IncompleteTypeInLetDecl, Error,
                           "`let` binding has incomplete type `{0}`.",
-                          std::string);
-        return context.emitter().Build(
-            type_node, IncompleteTypeInLetDecl,
-            context.sem_ir().StringifyType(cast_type_id));
+                          SemIR::TypeId);
+        return context.emitter().Build(type_node, IncompleteTypeInLetDecl,
+                                       cast_type_id);
       });
       // Create the instruction, but don't add it to a block until after we've
       // formed its initializer.

+ 2 - 2
toolchain/check/handle_call_expr.cpp

@@ -38,9 +38,9 @@ auto HandleCallExpr(Context& context, Parse::CallExprId parse_node) -> bool {
     auto callee_type_id = context.insts().Get(callee_id).type_id();
     if (callee_type_id != SemIR::TypeId::Error) {
       CARBON_DIAGNOSTIC(CallToNonCallable, Error,
-                        "Value of type `{0}` is not callable.", std::string);
+                        "Value of type `{0}` is not callable.", SemIR::TypeId);
       context.emitter().Emit(call_expr_parse_node, CallToNonCallable,
-                             context.sem_ir().StringifyType(callee_type_id));
+                             callee_type_id);
     }
     context.node_stack().Push(parse_node, SemIR::InstId::BuiltinError);
     return true;

+ 5 - 7
toolchain/check/handle_class.cpp

@@ -208,9 +208,8 @@ static auto DiagnoseBaseIsFinal(Context& context, Parse::NodeId parse_node,
   CARBON_DIAGNOSTIC(BaseIsFinal, Error,
                     "Deriving from final type `{0}`. Base type must be an "
                     "`abstract` or `base` class.",
-                    std::string);
-  context.emitter().Emit(parse_node, BaseIsFinal,
-                         context.sem_ir().StringifyType(base_type_id));
+                    SemIR::TypeId);
+  context.emitter().Emit(parse_node, BaseIsFinal, base_type_id);
 }
 
 // Checks that the specified base type is valid.
@@ -219,10 +218,9 @@ static auto CheckBaseType(Context& context, Parse::NodeId parse_node,
   auto base_type_id = ExprAsType(context, parse_node, base_expr_id);
   base_type_id = context.AsCompleteType(base_type_id, [&] {
     CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error,
-                      "Base `{0}` is an incomplete type.", std::string);
-    return context.emitter().Build(
-        parse_node, IncompleteTypeInBaseDecl,
-        context.sem_ir().StringifyType(base_type_id));
+                      "Base `{0}` is an incomplete type.", SemIR::TypeId);
+    return context.emitter().Build(parse_node, IncompleteTypeInBaseDecl,
+                                   base_type_id);
   });
 
   if (base_type_id == SemIR::TypeId::Error) {

+ 8 - 8
toolchain/check/handle_function.cpp

@@ -78,10 +78,11 @@ static auto BuildFunctionDecl(Context& context,
 
     return_type_id = context.AsCompleteType(return_type_id, [&] {
       CARBON_DIAGNOSTIC(IncompleteTypeInFunctionReturnType, Error,
-                        "Function returns incomplete type `{0}`.", std::string);
-      return context.emitter().Build(
-          return_node_and_id->first, IncompleteTypeInFunctionReturnType,
-          context.sem_ir().StringifyType(return_type_id));
+                        "Function returns incomplete type `{0}`.",
+                        SemIR::TypeId);
+      return context.emitter().Build(return_node_and_id->first,
+                                     IncompleteTypeInFunctionReturnType,
+                                     return_type_id);
     });
 
     if (!SemIR::GetInitRepr(context.sem_ir(), return_type_id)
@@ -241,10 +242,9 @@ auto HandleFunctionDefinitionStart(Context& context,
       CARBON_DIAGNOSTIC(
           IncompleteTypeInFunctionParam, Error,
           "Parameter has incomplete type `{0}` in function definition.",
-          std::string);
-      return context.emitter().Build(
-          param_id, IncompleteTypeInFunctionParam,
-          context.sem_ir().StringifyType(param.type_id()));
+          SemIR::TypeId);
+      return context.emitter().Build(param_id, IncompleteTypeInFunctionParam,
+                                     param.type_id());
     });
   }
 

+ 7 - 9
toolchain/check/handle_impl.cpp

@@ -115,10 +115,9 @@ static auto ExtendImpl(Context& context, Parse::AnyImplDeclId parse_node,
     CARBON_DIAGNOSTIC(
         ExtendUndefinedInterface, Error,
         "`extend impl` requires a definition for interface `{0}`.",
-        std::string);
-    auto diag =
-        context.emitter().Build(parse_node, ExtendUndefinedInterface,
-                                context.sem_ir().StringifyType(constraint_id));
+        SemIR::TypeId);
+    auto diag = context.emitter().Build(parse_node, ExtendUndefinedInterface,
+                                        constraint_id);
     context.NoteUndefinedInterface(interface_type->interface_id, diag);
     diag.Emit();
     enclosing_scope.has_error = true;
@@ -198,14 +197,13 @@ auto HandleImplDefinitionStart(Context& context,
 
   if (impl_info.definition_id.is_valid()) {
     CARBON_DIAGNOSTIC(ImplRedefinition, Error,
-                      "Redefinition of `impl {0} as {1}`.", std::string,
-                      std::string);
+                      "Redefinition of `impl {0} as {1}`.", SemIR::TypeId,
+                      SemIR::TypeId);
     CARBON_DIAGNOSTIC(ImplPreviousDefinition, Note,
                       "Previous definition was here.");
     context.emitter()
-        .Build(parse_node, ImplRedefinition,
-               context.sem_ir().StringifyType(impl_info.self_id),
-               context.sem_ir().StringifyType(impl_info.constraint_id))
+        .Build(parse_node, ImplRedefinition, impl_info.self_id,
+               impl_info.constraint_id)
         .Note(impl_info.definition_id, ImplPreviousDefinition)
         .Emit();
   } else {

+ 7 - 8
toolchain/check/handle_index.cpp

@@ -26,11 +26,10 @@ static auto ValidateTupleIndex(Context& context, Parse::NodeId parse_node,
     CARBON_DIAGNOSTIC(
         TupleIndexOutOfBounds, Error,
         "Tuple element index `{0}` is past the end of type `{1}`.",
-        llvm::APSInt, std::string);
-    context.emitter().Emit(
-        parse_node, TupleIndexOutOfBounds,
-        llvm::APSInt(index_val, /*isUnsigned=*/true),
-        context.sem_ir().StringifyType(operand_inst.type_id()));
+        llvm::APSInt, SemIR::TypeId);
+    context.emitter().Emit(parse_node, TupleIndexOutOfBounds,
+                           llvm::APSInt(index_val, /*isUnsigned=*/true),
+                           operand_inst.type_id());
     return nullptr;
   }
   return &index_val;
@@ -110,9 +109,9 @@ auto HandleIndexExpr(Context& context, Parse::IndexExprId parse_node) -> bool {
     default: {
       if (operand_type_id != SemIR::TypeId::Error) {
         CARBON_DIAGNOSTIC(TypeNotIndexable, Error,
-                          "Type `{0}` does not support indexing.", std::string);
-        context.emitter().Emit(parse_node, TypeNotIndexable,
-                               context.sem_ir().StringifyType(operand_type_id));
+                          "Type `{0}` does not support indexing.",
+                          SemIR::TypeId);
+        context.emitter().Emit(parse_node, TypeNotIndexable, operand_type_id);
       }
       context.node_stack().Push(parse_node, SemIR::InstId::BuiltinError);
       return true;

+ 8 - 9
toolchain/check/handle_name.cpp

@@ -121,10 +121,9 @@ auto HandleMemberAccessExpr(Context& context,
   if (!context.TryToCompleteType(base_type_id, [&] {
         CARBON_DIAGNOSTIC(IncompleteTypeInMemberAccess, Error,
                           "Member access into object of incomplete type `{0}`.",
-                          std::string);
-        return context.emitter().Build(
-            base_id, IncompleteTypeInMemberAccess,
-            context.sem_ir().StringifyType(base_type_id));
+                          SemIR::TypeId);
+        return context.emitter().Build(base_id, IncompleteTypeInMemberAccess,
+                                       base_type_id);
       })) {
     context.node_stack().Push(parse_node, SemIR::InstId::BuiltinError);
     return true;
@@ -223,10 +222,10 @@ auto HandleMemberAccessExpr(Context& context,
         }
       }
       CARBON_DIAGNOSTIC(QualifiedExprNameNotFound, Error,
-                        "Type `{0}` does not have a member `{1}`.", std::string,
-                        std::string);
+                        "Type `{0}` does not have a member `{1}`.",
+                        SemIR::TypeId, std::string);
       context.emitter().Emit(parse_node, QualifiedExprNameNotFound,
-                             context.sem_ir().StringifyType(base_type_id),
+                             base_type_id,
                              context.names().GetFormatted(name_id).str());
       break;
     }
@@ -237,9 +236,9 @@ auto HandleMemberAccessExpr(Context& context,
       if (base_type_id != SemIR::TypeId::Error) {
         CARBON_DIAGNOSTIC(QualifiedExprUnsupported, Error,
                           "Type `{0}` does not support qualified expressions.",
-                          std::string);
+                          SemIR::TypeId);
         context.emitter().Emit(parse_node, QualifiedExprUnsupported,
-                               context.sem_ir().StringifyType(base_type_id));
+                               base_type_id);
       }
       break;
     }

+ 3 - 4
toolchain/check/handle_operator.cpp

@@ -296,10 +296,9 @@ auto HandlePrefixOperatorStar(Context& context,
   } else if (type_id != SemIR::TypeId::Error) {
     CARBON_DIAGNOSTIC(DerefOfNonPointer, Error,
                       "Cannot dereference operand of non-pointer type `{0}`.",
-                      std::string);
-    auto builder =
-        context.emitter().Build(TokenOnly(parse_node), DerefOfNonPointer,
-                                context.sem_ir().StringifyType(type_id));
+                      SemIR::TypeId);
+    auto builder = context.emitter().Build(TokenOnly(parse_node),
+                                           DerefOfNonPointer, type_id);
     // TODO: Check for any facet here, rather than only a type.
     if (type_id == SemIR::TypeId::TypeType) {
       CARBON_DIAGNOSTIC(

+ 7 - 9
toolchain/check/return.cpp

@@ -35,14 +35,13 @@ static auto NoteNoReturnTypeProvided(Context::DiagnosticBuilder& diag,
 }
 
 // Produces a note describing the return type of the given function.
-static auto NoteReturnType(Context& context, Context::DiagnosticBuilder& diag,
+static auto NoteReturnType(Context::DiagnosticBuilder& diag,
                            const SemIR::Function& function) {
   CARBON_DIAGNOSTIC(ReturnTypeHereNote, Note,
-                    "Return type of function is `{0}`.", std::string);
+                    "Return type of function is `{0}`.", SemIR::TypeId);
   // TODO: This is using the location of the `fn` keyword. Find the location of
   // the return type.
-  diag.Note(function.decl_id, ReturnTypeHereNote,
-            context.sem_ir().StringifyType(function.return_type_id));
+  diag.Note(function.decl_id, ReturnTypeHereNote, function.return_type_id);
 }
 
 // Produces a note pointing at the currently in scope `returned var`.
@@ -73,11 +72,10 @@ auto CheckReturnedVar(Context& context, Parse::NodeId returned_node,
     CARBON_DIAGNOSTIC(ReturnedVarWrongType, Error,
                       "Type `{0}` of `returned var` does not match "
                       "return type of enclosing function.",
-                      std::string);
+                      SemIR::TypeId);
     auto diag =
-        context.emitter().Build(type_node, ReturnedVarWrongType,
-                                context.sem_ir().StringifyType(type_id));
-    NoteReturnType(context, diag, function);
+        context.emitter().Build(type_node, ReturnedVarWrongType, type_id);
+    NoteReturnType(diag, function);
     diag.Emit();
     return SemIR::InstId::BuiltinError;
   }
@@ -110,7 +108,7 @@ auto BuildReturnWithNoExpr(Context& context,
     CARBON_DIAGNOSTIC(ReturnStatementMissingExpr, Error,
                       "Missing return value.");
     auto diag = context.emitter().Build(parse_node, ReturnStatementMissingExpr);
-    NoteReturnType(context, diag, function);
+    NoteReturnType(diag, function);
     diag.Emit();
   }
 

+ 68 - 8
toolchain/diagnostics/diagnostic_emitter.h

@@ -136,6 +136,14 @@ class DiagnosticConsumer {
   virtual auto Flush() -> void {}
 };
 
+// Known diagnostic type translations. These are enumerated because `llvm::Any`
+// doesn't expose the contained type; instead, we infer it from a given
+// diagnostic.
+enum class DiagnosticTypeTranslation : int8_t {
+  None,
+  TypeId,
+};
+
 // An interface that can translate some representation of a location into a
 // diagnostic location.
 //
@@ -147,10 +155,46 @@ class DiagnosticLocationTranslator {
   virtual ~DiagnosticLocationTranslator() = default;
 
   virtual auto GetLocation(LocationT loc) -> DiagnosticLocation = 0;
+
+  // Translates arg types as needed. Not all uses support translation, so the
+  // default simply errors.
+  virtual auto TranslateArg(DiagnosticTypeTranslation translation,
+                            llvm::Any /*arg*/) const -> llvm::Any {
+    CARBON_FATAL() << "Unexpected call to TranslateArg: "
+                   << static_cast<int8_t>(translation);
+  }
+};
+
+template <typename StorageTypeT, DiagnosticTypeTranslation TranslationV>
+struct DiagnosticTypeInfo {
+  using StorageType = StorageTypeT;
+  static constexpr DiagnosticTypeTranslation Translation = TranslationV;
 };
 
 namespace Internal {
 
+// Determines whether there's a DiagnosticType member on Arg.
+template <typename Arg>
+concept HasDiagnosticType =
+    requires { std::type_identity<typename Arg::DiagnosticType>(); };
+
+// The default implementation with no translation.
+template <typename Arg, typename X = int>
+struct DiagnosticTypeForArg {
+  using StorageType = Arg;
+  static constexpr DiagnosticTypeTranslation Translation =
+      DiagnosticTypeTranslation::None;
+};
+
+// Exposes a custom translation for an argument type.
+template <typename Arg>
+  requires HasDiagnosticType<Arg>
+struct DiagnosticTypeForArg<Arg> {
+  using StorageType = Arg::DiagnosticType::StorageType;
+  static constexpr DiagnosticTypeTranslation Translation =
+      Arg::DiagnosticType::Translation;
+};
+
 // Use the DIAGNOSTIC macro to instantiate this.
 // This stores static information about a diagnostic category.
 template <typename... Args>
@@ -184,16 +228,18 @@ struct DiagnosticBase {
   inline auto FormatFnImpl(const DiagnosticMessage& message,
                            std::index_sequence<N...> /*indices*/) const
       -> std::string {
-    assert(message.format_args.size() == sizeof...(Args));
-    return llvm::formatv(message.format.data(),
-                         llvm::any_cast<Args>(message.format_args[N])...);
+    CARBON_CHECK(message.format_args.size() == sizeof...(Args));
+    return llvm::formatv(
+        message.format.data(),
+        llvm::any_cast<typename DiagnosticTypeForArg<Args>::StorageType>(
+            message.format_args[N])...);
   }
 };
 
 // Disable type deduction based on `args`; the type of `diagnostic_base`
 // determines the diagnostic's parameter types.
 template <typename Arg>
-using NoTypeDeduction = std::common_type_t<Arg>;
+using NoTypeDeduction = std::type_identity_t<Arg>;
 
 }  // namespace Internal
 
@@ -232,8 +278,9 @@ class DiagnosticEmitter {
               Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
       CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note)
           << static_cast<int>(diagnostic_base.Level);
-      diagnostic_.notes.push_back(MakeMessage(
-          emitter_, location, diagnostic_base, {llvm::Any(args)...}));
+      diagnostic_.notes.push_back(
+          MakeMessage(emitter_, location, diagnostic_base,
+                      {emitter_->MakeAny<Args>(args)...}));
       return *this;
     }
 
@@ -296,7 +343,7 @@ class DiagnosticEmitter {
   auto Emit(LocationT location,
             const Internal::DiagnosticBase<Args...>& diagnostic_base,
             Internal::NoTypeDeduction<Args>... args) -> void {
-    DiagnosticBuilder(this, location, diagnostic_base, {llvm::Any(args)...})
+    DiagnosticBuilder(this, location, diagnostic_base, {MakeAny<Args>(args)...})
         .Emit();
   }
 
@@ -311,7 +358,7 @@ class DiagnosticEmitter {
              const Internal::DiagnosticBase<Args...>& diagnostic_base,
              Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder {
     return DiagnosticBuilder(this, location, diagnostic_base,
-                             {llvm::Any(args)...});
+                             {MakeAny<Args>(args)...});
   }
 
  private:
@@ -340,6 +387,19 @@ class DiagnosticEmitter {
     DiagnosticEmitter* emitter_;
   };
 
+  // Converts an argument to llvm::Any for storage, handling input to storage
+  // type translation when needed.
+  template <typename Arg>
+  auto MakeAny(Arg arg) -> llvm::Any {
+    if constexpr (Internal::DiagnosticTypeForArg<Arg>::Translation ==
+                  DiagnosticTypeTranslation::None) {
+      return arg;
+    } else {
+      return translator_->TranslateArg(
+          Internal::DiagnosticTypeForArg<Arg>::Translation, arg);
+    }
+  }
+
   template <typename LocT, typename AnnotateFn>
   friend class DiagnosticAnnotationScope;
 

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -22,6 +22,7 @@ cc_library(
         "//common:ostream",
         "//toolchain/base:index_base",
         "//toolchain/base:value_store",
+        "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/sem_ir:builtin_kind",
     ],
 )

+ 4 - 0
toolchain/sem_ir/ids.h

@@ -9,6 +9,7 @@
 #include "common/ostream.h"
 #include "toolchain/base/index_base.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/sem_ir/builtin_kind.h"
 
 namespace Carbon::SemIR {
@@ -416,6 +417,9 @@ constexpr InstBlockId InstBlockId::GlobalInit = InstBlockId(2);
 // The ID of a type.
 struct TypeId : public IdBase, public Printable<TypeId> {
   using ValueType = TypeInfo;
+  // StringifyType() is used for diagnostics.
+  using DiagnosticType =
+      DiagnosticTypeInfo<std::string, DiagnosticTypeTranslation::TypeId>;
 
   // The builtin TypeType.
   static const TypeId TypeType;