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

Code before:
        if (context.types().Is<SemIR::FacetType>(type_of_expr_id)) {
          CARBON_DIAGNOSTIC(
              ConversionFailureFacetToFacet, Error,
              "cannot{0:| implicitly} convert type {1} that implements {2} to "
              "type implementing {3}{0: with `as`|}",
              BoolAsSelect, InstIdAsType, TypeOfInstId, SemIR::TypeId);
          return context.emitter().Build(loc_id, ConversionFailureFacetToFacet,
                                         explicit_as, expr_id, expr_id,
                                         target.type_id);
        } else if (auto facet_access_type =
                       context.insts().TryGetAs<SemIR::FacetAccessType>(
                           expr_id)) {
          CARBON_DIAGNOSTIC(
              ConversionFailureFacetAccessTypeToFacet, Error,
              "cannot{0:| implicitly} convert type {1} that implements {2} to "
              "type implementing {3}{0: with `as`|}",
              BoolAsSelect, InstIdAsType, TypeOfInstId, SemIR::TypeId);
          return context.emitter().Build(
              loc_id, ConversionFailureFacetAccessTypeToFacet, explicit_as,
              expr_id, facet_access_type->facet_value_inst_id, target.type_id);
        } else if (type_of_expr_id == SemIR::TypeType::SingletonTypeId) {
          CARBON_DIAGNOSTIC(
              ConversionFailureTypeToFacet, Error,
              "cannot{0:| implicitly} convert type {1} to type implementing "
              "{2}{0: with `as`|}; type {1} does not implement the required "
              "interface(s)",
              BoolAsSelect, InstIdAsType, SemIR::TypeId);
          return context.emitter().Build(loc_id, ConversionFailureTypeToFacet,
                                         explicit_as, expr_id, target.type_id);
        } else {
          CARBON_DIAGNOSTIC(
              ConversionFailureNonTypeToTypeOrFacet, Error,
              "cannot{0:| implicitly} convert non-type value of type {1} to "
              "{2:|type implementing }{3}{0: with `as`|}",
              BoolAsSelect, TypeOfInstId, BoolAsSelect, SemIR::TypeId);
          return context.emitter().Build(
              loc_id, ConversionFailureNonTypeToTypeOrFacet, explicit_as,
              expr_id, target.type_id == SemIR::TypeType::SingletonTypeId,
              target.type_id);
        }
      } else {
        // TODO: Should this message change to say "object of type" when
        // converting from a reference expression?
        CARBON_DIAGNOSTIC(ConversionFailure, Error,
                          "cannot{0:| implicitly} convert value of type {1} to "
                          "{2}{0: with `as`|}",
                          BoolAsSelect, TypeOfInstId, SemIR::TypeId);
        return context.emitter().Build(loc_id, ConversionFailure, explicit_as,
                                       expr_id, target.type_id);
      }
    });

Review comment (bug):
The `; type {1} does not implement the required interface(s)` part here is inconsistent with the prior diagnostic. Since they're diagnosing the same case with the only difference being whether the source type has locally-known constraints, it seems like the diagnostics ought to present the same information.

In discussion with @josh11b, we think the right place to produce this extra information about missing `impl`s would be to produce notes as part of impl lookup that describe exactly which impls are missing (as well as which impls we tried but failed to use and potentially why) -- though not as part of this PR. Once those notes are added, this extra information would be redundant, so let's omit it here:
```suggestion
              "{2}{0: with `as`|}",
```
(This diagnostic could then also be combined with the previous two with another select, but maybe it's not worthwhile.)

Code after:
              return context.emitter().BuildSuppressed();
            }
            CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
                              "forming value of incomplete type {0}",
                              SemIR::TypeId);
            CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
                              "invalid use of incomplete type {0}",
                              SemIR::TypeId);
            return context.emitter().Build(
                loc_id,
                target.kind == ConversionTarget::Value
                    ? IncompleteTypeInValueConversion
                    : IncompleteTypeInConversion,
                target.type_id);
          },
          [&] {
            if (!target.diagnose || !target.is_initializer()) {
              return context.emitter().BuildSuppressed();
            }
            CARBON_DIAGNOSTIC(AbstractTypeInInit, Error,
                              "initialization of abstract type {0}",
                              SemIR::TypeId);
            return context.emitter().Build(loc_id, AbstractTypeInInit,
                                           target.type_id);
          })) {
    return SemIR::ErrorInst::SingletonInstId;
  }

  // Check whether any builtin conversion applies.
  expr_id = PerformBuiltinConversion(context, loc_id, expr_id, target);
  if (expr_id == SemIR::ErrorInst::SingletonInstId) {
    return expr_id;
  }

  // If this is not a builtin conversion, try an `ImplicitAs` conversion.
  if (sem_ir.insts().Get(expr_id).type_id() != target.type_id) {
    SemIR::InstId interface_args[] = {
        context.types().GetInstId(target.type_id)};
    Operator op = {
        .interface_name = target.kind == ConversionTarget::ExplicitAs
                              ? llvm::StringLiteral("As")
                              : llvm::StringLiteral("ImplicitAs"),
        .interface_args_ref = interface_args,
        .op_name = "Convert",
    };
    expr_id = BuildUnaryOperator(context, loc_id, op, expr_id, [&] {
      if (!target.diagnose) {
        return context.emitter().BuildSuppressed();
      }
      if (target.type_id == SemIR::TypeType::SingletonTypeId ||
          sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {