Support parse/check/lower for char (#5901)

toolchain/check/testdata/builtins/char/basics.carbon and
toolchain/lower/testdata/builtins/char.carbon are probably the most
interesting tests here. The parse tests is required because this adds a
new node kind, and we need coverage of it; but the attached info is
minor. There's a fair amount of test churn here because I'm adding the
Core.Char and Core.CharLiteral types as new singletons.

My intent here is that `CharId` is always a unicode code point, even
when the type is a `Char` and thus must be a single UTF-8 code unit
(single byte). This mainly means the stored value of a `CharValue` can
be printed internally without knowing the type.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Jon Ross-Perkins
2025-08-06 20:39:01 +00:00
committed by GitHub
co-authored by josh11b Richard Smith
parent 9c7e0f6bd5
commit 37d5046ceb
109 changed files with 1520 additions and 1077 deletions
+36
View File
@@ -9,6 +9,7 @@
#include <optional>
#include <utility>
#include "llvm/Support/ConvertUTF.h"
#include "toolchain/base/canonical_value_store.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/action.h"
@@ -951,6 +952,28 @@ static auto PerformArrayIndex(EvalContext& eval_context, SemIR::ArrayIndex inst)
return eval_context.GetConstantValue(elements[index_val.getZExtValue()]);
}
// Performs a conversion between character types, diagnosing if the value
// doesn't fit in the destination type.
static auto PerformCheckedCharConvert(Context& context, SemIR::LocId loc_id,
SemIR::InstId arg_id,
SemIR::TypeId dest_type_id)
-> SemIR::ConstantId {
auto arg = context.insts().GetAs<SemIR::CharLiteralValue>(arg_id);
// Values over 0x80 require multiple code units in UTF-8.
if (arg.value.index >= 0x80) {
CARBON_DIAGNOSTIC(CharTooLargeForType, Error,
"character value {0} too large for type {1}",
SemIR::CharId, SemIR::TypeId);
context.emitter().Emit(loc_id, CharTooLargeForType, arg.value,
dest_type_id);
return SemIR::ErrorInst::ConstantId;
}
llvm::APInt int_val(8, arg.value.index, /*isSigned=*/false);
return MakeIntResult(context, dest_type_id, /*is_signed=*/false, int_val);
}
// Forms a constant int type as an evaluation result. Requires that width_id is
// constant.
static auto MakeIntTypeResult(Context& context, SemIR::LocId loc_id,
@@ -1590,6 +1613,10 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
return MakeFacetTypeResult(eval_context.context(), combined_info, phase);
}
case SemIR::BuiltinFunctionKind::CharLiteralMakeType: {
return context.constant_values().Get(SemIR::CharLiteralType::TypeInstId);
}
case SemIR::BuiltinFunctionKind::IntLiteralMakeType: {
return context.constant_values().Get(SemIR::IntLiteralType::TypeInstId);
}
@@ -1619,6 +1646,15 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
return context.constant_values().Get(SemIR::BoolType::TypeInstId);
}
// Character conversions.
case SemIR::BuiltinFunctionKind::CharConvertChecked: {
if (phase != Phase::Concrete) {
return MakeConstantResult(context, call, phase);
}
return PerformCheckedCharConvert(context, loc_id, arg_ids[0],
call.type_id);
}
// Integer conversions.
case SemIR::BuiltinFunctionKind::IntConvert: {
if (phase != Phase::Concrete) {