Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/prelude/types/char.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -27,3 +29,15 @@ impl CharLiteral() as ImplicitAs(Char) {
impl CharLiteral() as As(Char) {
fn Convert[self: Self]() -> Char = "char.convert_checked";
}

impl Int(8) as As(Char) {
fn Convert[self: Self]() -> Char = "int.convert_char";
}

impl Int(32) as As(Char) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this generic for all integer sizes, like it is for u8?

final impl forall [From:! IntLiteral(), To:! IntLiteral()] UInt(From) as As(UInt(To)) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @danakj

I had issues with this kind of implementation, but I managed to get support working for i32, i8, and i64! However, I’ll work on adapting it to what you suggested in the future.

fn Convert[self: Self]() -> Char = "int.convert_char";
}

impl Int(64) as As(Char) {
fn Convert[self: Self]() -> Char = "int.convert_char";
}
34 changes: 34 additions & 0 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,34 @@ static auto MakeFloatTypeResult(Context& context, SemIR::LocId loc_id,
return MakeConstantResult(context, result, phase);
}

//// Performs a conversion from integer to character type.
static auto PerformIntToCharConvert(Context& context, SemIR::LocId loc_id,
SemIR::InstId arg_id,
SemIR::TypeId dest_type_id)
-> SemIR::ConstantId {
auto arg = context.insts().GetAs<SemIR::IntValue>(arg_id);
auto arg_val = context.ints().Get(arg.int_id);

auto [is_signed, bit_width_id] =
context.sem_ir().types().GetIntTypeInfo(dest_type_id);

int64_t value = arg_val.getSExtValue();

if (!is_signed && value < 0) {
CARBON_DIAGNOSTIC(
NegativeIntInUnsignedTypeInCast, Error,
"negative integer value {0} converted to unsigned type {1}", TypedInt,
SemIR::TypeId);
context.emitter().Emit(loc_id, NegativeIntInUnsignedTypeInCast,
{.type = arg.type_id, .value = arg_val},
dest_type_id);
return SemIR::ErrorInst::ConstantId;
}

llvm::APInt int_val(8, value, /*isSigned=*/false);
return MakeIntResult(context, dest_type_id, /*is_signed=*/false, int_val);
}

// Performs a conversion between integer types, truncating if the value doesn't
// fit in the destination type.
static auto PerformIntConvert(Context& context, SemIR::InstId arg_id,
Expand Down Expand Up @@ -1820,6 +1848,12 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
}

// Integer conversions.
case SemIR::BuiltinFunctionKind::IntConvertChar: {
if (phase != Phase::Concrete) {
return MakeConstantResult(context, call, phase);
}
return PerformIntToCharConvert(context, loc_id, arg_ids[0], call.type_id);
}
case SemIR::BuiltinFunctionKind::IntConvert: {
if (phase != Phase::Concrete) {
return MakeConstantResult(context, call, phase);
Expand Down
44 changes: 44 additions & 0 deletions toolchain/check/testdata/builtins/int/convert.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,47 @@ let convert_not_constant: i16 = IntLiteralToInt16(not_constant);
// CHECK:STDOUT: return %Int32ToInt64.call to %return
// CHECK:STDOUT: }
// CHECK:STDOUT:

// --- int_to_char_basic.carbon

library "[[@TEST_NAME]]";
import library "int_ops";

fn F() {
// Basic ASCII range
Test(Int32ToChar(0)) as Expect(0 as char);
Test(Int32ToChar(10)) as Expect(10 as char);
Test(Int32ToChar(32)) as Expect(32 as char);
Test(Int32ToChar(65)) as Expect(65 as char);
Test(Int32ToChar(97)) as Expect(97 as char);
Test(Int32ToChar(0x7F)) as Expect(0x7F as char);

// UTF-8 single-byte values (128–255)
Test(Int32ToChar(0x80)) as Expect(0x80 as char);
Test(Int32ToChar(0x90)) as Expect(0x90 as char);
Test(Int32ToChar(0xA0)) as Expect(0xA0 as char);
Test(Int32ToChar(0xC0)) as Expect(0xC0 as char);
Test(Int32ToChar(0xE0)) as Expect(0xE0 as char);
Test(Int32ToChar(0xFF)) as Expect(0xFF as char);

// Negative values → must error (same style as sign_extend test)
// CHECK:STDERR: int_to_char_basic.carbon:[[@LINE+3]]:30: error: cannot convert negative integer to `char` [InvalidIntToChar]
// CHECK:STDERR: var bad1: char = (-1 as char);
// CHECK:STDERR: ^~~
var bad1: char = (-1 as char);

// CHECK:STDERR: int_to_char_basic.carbon:[[@LINE+3]]:30: error: cannot convert negative integer to `char` [InvalidIntToChar]
// CHECK:STDERR: var bad2: char = (-10 as char);
// CHECK:STDERR: ^~~
var bad2: char = (-10 as char);

// CHECK:STDERR: int_to_char_basic.carbon:[[@LINE+3]]:30: error: cannot convert negative integer to `char` [InvalidIntToChar]
// CHECK:STDERR: var bad3: char = (-255 as char);
// CHECK:STDERR: ^~~
var bad3: char = (-255 as char);

// CHECK:STDERR: int_to_char_basic.carbon:[[@LINE+3]]:30: error: cannot convert negative integer to `char` [InvalidIntToChar]
// CHECK:STDERR: var bad4: char = (-2147483648 as char);
// CHECK:STDERR: ^~~
var bad4: char = (-2147483648 as char);
}
1 change: 1 addition & 0 deletions toolchain/diagnostics/diagnostic_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ CARBON_DIAGNOSTIC_KIND(IntWidthNotPositive)
CARBON_DIAGNOSTIC_KIND(IntWidthTooLarge)
CARBON_DIAGNOSTIC_KIND(InvalidArrayExpr)
CARBON_DIAGNOSTIC_KIND(NegativeIntInUnsignedType)
CARBON_DIAGNOSTIC_KIND(NegativeIntInUnsignedTypeInCast)
CARBON_DIAGNOSTIC_KIND(NonConstantCallToCompTimeOnlyFunction)
CARBON_DIAGNOSTIC_KIND(CompTimeOnlyFunctionHere)
CARBON_DIAGNOSTIC_KIND(SelfOutsideImplicitParamList)
Expand Down
1 change: 1 addition & 0 deletions toolchain/lower/handle_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,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]),
Expand Down
5 changes: 5 additions & 0 deletions toolchain/sem_ir/builtin_function_kind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,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>};
Expand Down Expand Up @@ -785,6 +789,7 @@ auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir,
return true;

case IntConvert:
case IntConvertChar:
case IntSNegate:
case IntComplement:
case IntSAdd:
Expand Down
1 change: 1 addition & 0 deletions toolchain/sem_ir/builtin_function_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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.
Expand Down