Browse Source

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 <danakj@orodu.net>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Vinicius Silva 3 months ago
parent
commit
f42352759f

+ 6 - 0
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";
+}

+ 6 - 0
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);

+ 38 - 0
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 {

+ 1 - 0
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]),

+ 5 - 0
toolchain/sem_ir/builtin_function_kind.cpp

@@ -446,6 +446,10 @@ constexpr BuiltinInfo CharConvertChecked = {
     "char.convert_checked",
     ValidateSignature<auto(CharLiteral)->CharCompatible>};
 
+// Converts from an integer type to a char-compatible type (u8/adapted Char).
+constexpr BuiltinInfo IntConvertChar = {
+    "int.convert_char", ValidateSignature<auto(AnyInt)->CharCompatible>};
+
 // Converts between integer types, truncating if necessary.
 constexpr BuiltinInfo IntConvert = {"int.convert",
                                     ValidateSignature<auto(AnyInt)->AnyInt>};
@@ -816,6 +820,7 @@ auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir,
       return true;
 
     case IntConvert:
+    case IntConvertChar:
     case IntSNegate:
     case IntComplement:
     case IntSAdd:

+ 1 - 0
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.