From f42352759f72b3a9cea4723a1bde0f1f43e820bf Mon Sep 17 00:00:00 2001 From: Vinicius Silva Date: Wed, 21 Jan 2026 18:48:49 -0300 Subject: [PATCH] Adding support for `UInt`-to-`char` conversion (#6425) This pull request adds support for integer-to-char conversion, allowing the compiler to correctly handle character casting, implementing part of the issue #5922. ```carbon import Core library "io"; fn Run() -> i32 { var i : i32 = 65; var ch: char = (i as char); // Support implemented! Core.PrintChar(ch); // Print 'A' return 0; } ``` --------- Co-authored-by: Dana Jansens Co-authored-by: Carbon Infra Bot --- core/prelude/types/char.carbon | 6 +++ toolchain/check/eval.cpp | 6 +++ .../testdata/builtins/int/convert.carbon | 38 +++++++++++++++++++ toolchain/lower/handle_call.cpp | 1 + toolchain/sem_ir/builtin_function_kind.cpp | 5 +++ toolchain/sem_ir/builtin_function_kind.def | 1 + 6 files changed, 57 insertions(+) diff --git a/core/prelude/types/char.carbon b/core/prelude/types/char.carbon index 426875527466..74f3ac81e13d 100644 --- a/core/prelude/types/char.carbon +++ b/core/prelude/types/char.carbon @@ -8,6 +8,8 @@ import library "prelude/copy"; import library "prelude/destroy"; import library "prelude/operators"; import library "prelude/types/uint"; +import library "prelude/types/int"; +import library "prelude/types/int_literal"; fn CharLiteral() -> type = "char_literal.make_type"; @@ -27,3 +29,7 @@ impl CharLiteral() as ImplicitAs(Char) { impl CharLiteral() as As(Char) { fn Convert[self: Self]() -> Char = "char.convert_checked"; } + +impl forall [From:! IntLiteral()] UInt(From) as As(Char) { + fn Convert[self: Self]() -> Char = "int.convert_char"; +} diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 45b28e9d427b..ef6da161538e 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -1866,6 +1866,12 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context, } // Integer conversions. + case SemIR::BuiltinFunctionKind::IntConvertChar: { + if (phase != Phase::Concrete) { + return MakeConstantResult(context, call, phase); + } + return PerformIntConvert(context, arg_ids[0], call.type_id); + } case SemIR::BuiltinFunctionKind::IntConvert: { if (phase != Phase::Concrete) { return MakeConstantResult(context, call, phase); diff --git a/toolchain/check/testdata/builtins/int/convert.carbon b/toolchain/check/testdata/builtins/int/convert.carbon index 6df04416d4b1..80581eb41a58 100644 --- a/toolchain/check/testdata/builtins/int/convert.carbon +++ b/toolchain/check/testdata/builtins/int/convert.carbon @@ -37,6 +37,12 @@ fn Uint32ToUint64(a: u32) -> u64 = "int.convert"; fn Int32ToIntLiteral(a: i32) -> Core.IntLiteral() = "int.convert"; fn Uint32ToIntLiteral(a: u32) -> Core.IntLiteral() = "int.convert"; +// char conversion +fn UInt8ToChar(a: u8) -> u8 = "int.convert_char"; +fn UInt16ToChar(a: u16) -> u8 = "int.convert_char"; +fn UInt32ToChar(a: u32) -> u8 = "int.convert_char"; +fn UInt64ToChar(a: u64) -> u8 = "int.convert_char"; + class Expect[T:! type](N:! T) {} fn Test[T:! type](N:! T) -> Expect(N) { return {}; } @@ -249,6 +255,38 @@ let not_constant: Core.IntLiteral() = 0; // CHECK:STDERR: let convert_not_constant: i16 = IntLiteralToInt16(not_constant); + +// --- char_conversion.carbon + +library "[[@TEST_NAME]]"; +import library "int_ops"; + +fn F() { + // u8 \u2192 char + Test(UInt8ToChar(0x00)) as Expect(0x00 as u8); + Test(UInt8ToChar(0x80)) as Expect(0x80 as u8); + Test(UInt8ToChar(0xFF)) as Expect(0xFF as u8); + + // u16 \u2192 i64 + Test(UInt16ToChar(0x0000)) as Expect(0x00 as u8); + Test(UInt16ToChar(0xFF12)) as Expect(0x12 as u8); + Test(UInt16ToChar(0xFFFF)) as Expect(0xFF as u8); + + // u32 \u2192 char + Test(UInt32ToChar(0x0000_0000)) as Expect(0x00 as u8); + Test(UInt32ToChar(0x8000_0012)) as Expect(0x12 as u8); + Test(UInt32ToChar(0xFFFF_FF12)) as Expect(0x12 as u8); + Test(UInt32ToChar(0xFFFF_FFFF)) as Expect(0xFF as u8); + + // u64 \u2192 char + Test(UInt64ToChar(0x0)) as Expect(0x00 as u8); + Test(UInt64ToChar(0x8000_0000_0000_0012)) as Expect(0x12 as u8); + Test(UInt64ToChar(0xFFFF_FFFF_FFFF_FF12)) as Expect(0x12 as u8); + + Test(UInt64ToChar(0xFFFF_FFFF_FFFF_FFFF)) as Expect(0xFF as u8); + +} + // CHECK:STDOUT: --- runtime_call.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index 146a29a60ea2..efed270930af 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -359,6 +359,7 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id, context.SetLocal(inst_id, context.GetTypeAsValue()); return; + case SemIR::BuiltinFunctionKind::IntConvertChar: case SemIR::BuiltinFunctionKind::IntConvert: { context.SetLocal(inst_id, CreateExtOrTrunc(context, context.GetValue(arg_ids[0]), diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index e0b91939d6f3..724ff5757cae 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -446,6 +446,10 @@ constexpr BuiltinInfo CharConvertChecked = { "char.convert_checked", ValidateSignatureCharCompatible>}; +// Converts from an integer type to a char-compatible type (u8/adapted Char). +constexpr BuiltinInfo IntConvertChar = { + "int.convert_char", ValidateSignatureCharCompatible>}; + // Converts between integer types, truncating if necessary. constexpr BuiltinInfo IntConvert = {"int.convert", ValidateSignatureAnyInt>}; @@ -816,6 +820,7 @@ auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir, return true; case IntConvert: + case IntConvertChar: case IntSNegate: case IntComplement: case IntSAdd: diff --git a/toolchain/sem_ir/builtin_function_kind.def b/toolchain/sem_ir/builtin_function_kind.def index c33f34464603..b0251da7b0ae 100644 --- a/toolchain/sem_ir/builtin_function_kind.def +++ b/toolchain/sem_ir/builtin_function_kind.def @@ -47,6 +47,7 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(CharConvertChecked) // Integer conversion. CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvert) +CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvertChar) CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvertChecked) // Integer arithmetic.