Skip to content

Commit 98be5b6

Browse files
committed
Address feedback
1 parent ce10970 commit 98be5b6

File tree

7 files changed

+144
-0
lines changed

7 files changed

+144
-0
lines changed

core/prelude/types/string.carbon

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import library "prelude/copy";
88
import library "prelude/destroy";
99
import library "prelude/types/char";
1010
import library "prelude/types/uint";
11+
import library "prelude/operators/index";
12+
import library "prelude/types/int";
13+
14+
class String;
15+
16+
// Forward declaration for builtin function
17+
fn StringAt(s: String, index: i32) -> Char;
1118

1219
class String {
1320
fn Size[self: Self]() -> u64 { return self.size; }
@@ -16,8 +23,15 @@ class String {
1623
fn Op[self: Self]() -> Self { return {.ptr = self.ptr, .size = self.size}; }
1724
}
1825

26+
impl as IndexWith(i32) where .ElementType = Char {
27+
fn At[self: Self](subscript: i32) -> Char {
28+
return StringAt(self, subscript);
29+
}
30+
}
1931
// TODO: This should be an array iterator.
2032
private var ptr: Char*;
2133
// TODO: This should be a word-sized integer.
2234
private var size: u64;
2335
}
36+
37+
fn StringAt(s: String, index: i32) -> Char = "string.at";

toolchain/check/eval.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,63 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
16871687
return context.constant_values().Get(arg_ids[0]);
16881688
}
16891689

1690+
case SemIR::BuiltinFunctionKind::StringAt: {
1691+
Phase phase = Phase::Concrete;
1692+
auto str_id = GetConstantValue(eval_context, arg_ids[0], &phase);
1693+
auto index_id = GetConstantValue(eval_context, arg_ids[1], &phase);
1694+
1695+
if (phase != Phase::Concrete) {
1696+
return MakeNonConstantResult(phase);
1697+
}
1698+
1699+
auto str_struct = eval_context.insts().GetAs<SemIR::StructValue>(str_id);
1700+
auto elements = eval_context.inst_blocks().Get(str_struct.elements_id);
1701+
CARBON_CHECK(elements.size() == 2, "String struct should have 2 fields.");
1702+
1703+
auto ptr_const_id = eval_context.constant_values().Get(elements[0]);
1704+
auto string_literal = eval_context.insts().GetAs<SemIR::StringLiteral>(
1705+
eval_context.constant_values().GetInstId(ptr_const_id));
1706+
1707+
auto string_value = eval_context.sem_ir().string_literal_values().Get(
1708+
string_literal.string_literal_id);
1709+
1710+
auto index_inst = eval_context.insts().GetAs<SemIR::IntValue>(index_id);
1711+
const auto& index_val = eval_context.ints().Get(index_inst.int_id);
1712+
1713+
// Check bounds
1714+
if (index_val.isNegative()) {
1715+
CARBON_DIAGNOSTIC(ArrayIndexNegative, Error, "index `{0}` is negative.",
1716+
TypedInt);
1717+
eval_context.emitter().Emit(
1718+
eval_context.GetDiagnosticLoc(index_id), ArrayIndexNegative,
1719+
{.type = eval_context.insts().Get(index_id).type_id(),
1720+
.value = index_val});
1721+
return SemIR::ErrorInst::ConstantId;
1722+
}
1723+
1724+
// Check for out of bounds
1725+
if (index_val.getActiveBits() > 64 ||
1726+
index_val.getZExtValue() >= string_value.size()) {
1727+
CARBON_DIAGNOSTIC(StringAtIndexOutOfBounds, Error,
1728+
"string index `{0}` is past the end of the string.",
1729+
TypedInt);
1730+
eval_context.emitter().Emit(
1731+
eval_context.GetDiagnosticLoc(index_id), StringAtIndexOutOfBounds,
1732+
{.type = eval_context.insts().Get(index_id).type_id(),
1733+
.value = index_val});
1734+
return SemIR::ErrorInst::ConstantId;
1735+
}
1736+
1737+
auto char_value =
1738+
static_cast<uint8_t>(string_value[index_val.getZExtValue()]);
1739+
1740+
auto int_id = eval_context.ints().Add(
1741+
llvm::APSInt(llvm::APInt(32, char_value), /*isUnsigned=*/false));
1742+
return MakeConstantResult(
1743+
eval_context.context(),
1744+
SemIR::IntValue{.type_id = call.type_id, .int_id = int_id}, phase);
1745+
}
1746+
16901747
case SemIR::BuiltinFunctionKind::PrintChar:
16911748
case SemIR::BuiltinFunctionKind::PrintInt:
16921749
case SemIR::BuiltinFunctionKind::ReadChar:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
5+
// AUTOUPDATE
6+
// TIP: To test this file alone, run:
7+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/operators/overloaded/string_indexing.carbon
8+
// TIP: To dump output, run:
9+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/operators/overloaded/string_indexing.carbon
10+
11+
// --- fail_wrong_type.carbon
12+
13+
fn TestWrongType() {
14+
var x: i32 = 42;
15+
// CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:22: error: cannot access member of interface `Core.IndexWith(Core.IntLiteral)` in type `i32` that does not implement that interface [MissingImplInMemberAccess]
16+
// CHECK:STDERR: var c: Core.Char = x[0];
17+
// CHECK:STDERR: ^~~~
18+
// CHECK:STDERR:
19+
var c: Core.Char = x[0];
20+
}
21+

toolchain/diagnostics/diagnostic_kind.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ CARBON_DIAGNOSTIC_KIND(AddrOnNonSelfParam)
397397
CARBON_DIAGNOSTIC_KIND(AddrOnNonPointerType)
398398
CARBON_DIAGNOSTIC_KIND(ArrayBoundTooLarge)
399399
CARBON_DIAGNOSTIC_KIND(ArrayBoundNegative)
400+
CARBON_DIAGNOSTIC_KIND(ArrayIndexNegative)
400401
CARBON_DIAGNOSTIC_KIND(ArrayIndexOutOfBounds)
401402
CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch)
402403
CARBON_DIAGNOSTIC_KIND(ArrayInitFromExprArgCountMismatch)
@@ -450,6 +451,7 @@ CARBON_DIAGNOSTIC_KIND(NegativeIntInUnsignedType)
450451
CARBON_DIAGNOSTIC_KIND(NonConstantCallToCompTimeOnlyFunction)
451452
CARBON_DIAGNOSTIC_KIND(CompTimeOnlyFunctionHere)
452453
CARBON_DIAGNOSTIC_KIND(SelfOutsideImplicitParamList)
454+
CARBON_DIAGNOSTIC_KIND(StringAtIndexOutOfBounds)
453455
CARBON_DIAGNOSTIC_KIND(StringLiteralTooLong)
454456
CARBON_DIAGNOSTIC_KIND(StringLiteralTypeIncomplete)
455457
CARBON_DIAGNOSTIC_KIND(StringLiteralTypeUnexpected)

toolchain/lower/handle_call.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,34 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
318318
return;
319319
}
320320

321+
case SemIR::BuiltinFunctionKind::StringAt: {
322+
auto string_inst_id = arg_ids[0];
323+
auto* string_arg = context.GetValue(string_inst_id);
324+
325+
auto string_type_id = context.GetTypeIdOfInst(string_inst_id);
326+
auto* string_type = context.GetType(string_type_id);
327+
auto* string_value =
328+
context.builder().CreateLoad(string_type, string_arg, "string.load");
329+
330+
auto* string_ptr_field =
331+
context.builder().CreateExtractValue(string_value, {0}, "string.ptr");
332+
333+
auto* index_value = context.GetValue(arg_ids[1]);
334+
335+
auto* char_ptr = context.builder().CreateInBoundsGEP(
336+
llvm::Type::getInt8Ty(context.llvm_context()), string_ptr_field,
337+
index_value, "string.char_ptr");
338+
339+
auto* char_i8 = context.builder().CreateLoad(
340+
llvm::Type::getInt8Ty(context.llvm_context()), char_ptr,
341+
"string.char");
342+
343+
context.SetLocal(inst_id, context.builder().CreateZExt(
344+
char_i8, context.GetTypeOfInst(inst_id),
345+
"string.char.zext"));
346+
return;
347+
}
348+
321349
case SemIR::BuiltinFunctionKind::TypeAnd: {
322350
context.SetLocal(inst_id, context.GetTypeAsValue());
323351
return;

toolchain/sem_ir/builtin_function_kind.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,22 @@ struct AnyType {
177177
return true;
178178
}
179179
};
180+
// Constraint that checks if a type is Core.String.
181+
struct CoreStringType {
182+
static auto Check(const File& sem_ir, ValidateState& /*state*/,
183+
TypeId type_id) -> bool {
184+
auto type_inst_id = sem_ir.types().GetInstId(type_id);
185+
auto class_type = sem_ir.insts().TryGetAs<ClassType>(type_inst_id);
186+
if (!class_type) {
187+
// Not a string.
188+
return false;
189+
}
190+
191+
const auto& class_info = sem_ir.classes().Get(class_type->class_id);
192+
193+
return sem_ir.names().GetFormatted(class_info.name_id).str() == "String";
194+
}
195+
};
180196

181197
// Constraint that requires the type to be the type type.
182198
using Type = BuiltinType<TypeType::TypeInstId>;
@@ -322,6 +338,11 @@ constexpr BuiltinInfo PrintInt = {
322338
constexpr BuiltinInfo ReadChar = {"read.char",
323339
ValidateSignature<auto()->AnySizedInt>};
324340

341+
// Gets a character from a string at the given index.
342+
constexpr BuiltinInfo StringAt = {
343+
"string.at",
344+
ValidateSignature<auto(CoreStringType, AnySizedInt)->AnySizedInt>};
345+
325346
// Returns the `Core.CharLiteral` type.
326347
constexpr BuiltinInfo CharLiteralMakeType = {"char_literal.make_type",
327348
ValidateSignature<auto()->Type>};

toolchain/sem_ir/builtin_function_kind.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrimitiveCopy)
3030
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintChar)
3131
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintInt)
3232
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(ReadChar)
33+
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(StringAt)
3334

3435
// Type factories.
3536
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(CharLiteralMakeType)

0 commit comments

Comments
 (0)