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

Code before:
              "{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);
      }
    });

    // Pull a value directly out of the initializer if possible and wanted.
    if (expr_id != SemIR::ErrorInst::SingletonInstId &&
        CanUseValueOfInitializer(sem_ir, target.type_id, target.kind)) {
      expr_id = AddInst<SemIR::ValueOfInitializer>(
          context, loc_id, {.type_id = target.type_id, .init_id = expr_id});
    }
  }

  // Track that we performed a type conversion, if we did so.
  if (orig_expr_id != expr_id) {
    expr_id = AddInst<SemIR::Converted>(context, loc_id,
                                        {.type_id = target.type_id,
                                         .original_id = orig_expr_id,
                                         .result_id = expr_id});
  }

  // For `as`, don't perform any value category conversions. In particular, an
  // identity conversion shouldn't change the expression category.
  if (target.kind == ConversionTarget::ExplicitAs) {
    return expr_id;
  }

  // Now perform any necessary value category conversions.

Review comment (bug):
[No action requested]

As a general thought, perhaps the conversion between facet types should be treated like a `final impl`, so that we diagnose these first three cases in `PerformBuiltinConversion` rather than here. Doing these checks here in this PR makes sense though, given that it matches the current behavior.

(As noted below, we'll eventually want impl lookup to explain what went wrong, and that'll be easier to do if we produce the diagnostics as part of the code that performs impl lookup rather than after we try a user-defined conversion.)

Code after:
          })) {
    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)) {
        // TODO: Move this to PerformBuiltinConversion(). See
        // https://github.com/carbon-language/carbon-lang/issues/5122.
        return DiagnoseConversionFailureToConstraintValue(context, loc_id,
                                                          expr_id, target);
      } 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,
            target.kind == ConversionTarget::ExplicitAs, expr_id,
            target.type_id);
      }
    });

    // Pull a value directly out of the initializer if possible and wanted.
    if (expr_id != SemIR::ErrorInst::SingletonInstId &&
        CanUseValueOfInitializer(sem_ir, target.type_id, target.kind)) {
      expr_id = AddInst<SemIR::ValueOfInitializer>(
          context, loc_id, {.type_id = target.type_id, .init_id = expr_id});
    }