mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:50:14 +01:00
Added string indexing (#6329)
Implemented string indexing for Core.string Handles references or struct values. No runtime checks as per https://discord.com/channels/655572317891461132/655578254970716160/1431015866270748682 Part of #6270 --------- Co-authored-by: Dana Jansens <danakj@orodu.net>
This commit is contained in:
co-authored by
Dana Jansens
parent
35d505a985
commit
a848ae11e4
@@ -8,6 +8,9 @@ import library "prelude/copy";
|
||||
import library "prelude/destroy";
|
||||
import library "prelude/types/char";
|
||||
import library "prelude/types/uint";
|
||||
import library "prelude/operators/index";
|
||||
import library "prelude/types/int";
|
||||
import library "prelude/operators/as";
|
||||
|
||||
class String {
|
||||
fn Size[self: Self]() -> u64 { return self.size; }
|
||||
@@ -15,9 +18,12 @@ class String {
|
||||
impl as Copy {
|
||||
fn Op[self: Self]() -> Self { return {.ptr = self.ptr, .size = self.size}; }
|
||||
}
|
||||
|
||||
// TODO: This should be an array iterator.
|
||||
private var ptr: Char*;
|
||||
// TODO: This should be a word-sized integer.
|
||||
private var size: u64;
|
||||
}
|
||||
|
||||
impl forall [T:! ImplicitAs(i64)] String as IndexWith(T) where .ElementType = Char {
|
||||
fn At[self: Self](subscript: T) -> Char = "string.at";
|
||||
}
|
||||
|
||||
@@ -1715,6 +1715,64 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
|
||||
return context.constant_values().Get(arg_ids[0]);
|
||||
}
|
||||
|
||||
case SemIR::BuiltinFunctionKind::StringAt: {
|
||||
Phase phase = Phase::Concrete;
|
||||
auto str_id = GetConstantValue(eval_context, arg_ids[0], &phase);
|
||||
auto index_id = GetConstantValue(eval_context, arg_ids[1], &phase);
|
||||
|
||||
if (phase != Phase::Concrete) {
|
||||
return MakeNonConstantResult(phase);
|
||||
}
|
||||
|
||||
auto str_struct = eval_context.insts().GetAs<SemIR::StructValue>(str_id);
|
||||
auto elements = eval_context.inst_blocks().Get(str_struct.elements_id);
|
||||
// String struct has two fields: a pointer to the string data and the
|
||||
// length.
|
||||
CARBON_CHECK(elements.size() == 2, "String struct should have 2 fields.");
|
||||
|
||||
auto string_literal = eval_context.insts().GetAs<SemIR::StringLiteral>(
|
||||
eval_context.constant_values().GetConstantInstId(elements[0]));
|
||||
|
||||
const auto& string_value =
|
||||
eval_context.sem_ir().string_literal_values().Get(
|
||||
string_literal.string_literal_id);
|
||||
|
||||
auto index_inst = eval_context.insts().GetAs<SemIR::IntValue>(index_id);
|
||||
const auto& index_val = eval_context.ints().Get(index_inst.int_id);
|
||||
|
||||
if (index_val.isNegative()) {
|
||||
CARBON_DIAGNOSTIC(StringAtIndexNegative, Error,
|
||||
"index `{0}` is negative.", TypedInt);
|
||||
context.emitter().Emit(
|
||||
loc_id, StringAtIndexNegative,
|
||||
{.type = eval_context.insts().Get(index_id).type_id(),
|
||||
.value = index_val});
|
||||
return SemIR::ConstantId::NotConstant;
|
||||
}
|
||||
|
||||
if (index_val.getZExtValue() >= string_value.size()) {
|
||||
CARBON_DIAGNOSTIC(
|
||||
StringAtIndexOutOfBounds, Error,
|
||||
"string index `{0}` is out of bounds; string has length {1}.",
|
||||
TypedInt, size_t);
|
||||
context.emitter().Emit(
|
||||
loc_id, StringAtIndexOutOfBounds,
|
||||
{.type = eval_context.insts().Get(index_id).type_id(),
|
||||
.value = index_val},
|
||||
string_value.size());
|
||||
return SemIR::ConstantId::NotConstant;
|
||||
}
|
||||
|
||||
auto char_value =
|
||||
static_cast<uint8_t>(string_value[index_val.getZExtValue()]);
|
||||
|
||||
auto int_id = eval_context.ints().Add(
|
||||
llvm::APSInt(llvm::APInt(32, char_value), /*isUnsigned=*/false));
|
||||
return MakeConstantResult(
|
||||
eval_context.context(),
|
||||
SemIR::IntValue{.type_id = call.type_id, .int_id = int_id}, phase);
|
||||
}
|
||||
|
||||
case SemIR::BuiltinFunctionKind::PrintChar:
|
||||
case SemIR::BuiltinFunctionKind::PrintInt:
|
||||
case SemIR::BuiltinFunctionKind::ReadChar:
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// TIP: To test this file alone, run:
|
||||
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/operators/overloaded/string_indexing.carbon
|
||||
// TIP: To dump output, run:
|
||||
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/operators/overloaded/string_indexing.carbon
|
||||
|
||||
// --- fail_literal_negative_index.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn TestLiteralNegativeIndex() {
|
||||
// CHECK:STDERR: fail_literal_negative_index.carbon:[[@LINE+4]]:17: error: index `-1` is negative. [StringAtIndexNegative]
|
||||
// CHECK:STDERR: let c: char = "Test"[-1];
|
||||
// CHECK:STDERR: ^~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c: char = "Test"[-1];
|
||||
}
|
||||
|
||||
// --- fail_literal_out_of_bounds.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn TestLiteralOutOfBounds() {
|
||||
// CHECK:STDERR: fail_literal_out_of_bounds.carbon:[[@LINE+4]]:17: error: string index `4` is out of bounds; string has length 4. [StringAtIndexOutOfBounds]
|
||||
// CHECK:STDERR: let c: char = "Test"[4];
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c: char = "Test"[4];
|
||||
}
|
||||
|
||||
// --- fail_wrong_type.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn TestWrongType() {
|
||||
class C{}
|
||||
//@dump-sem-ir-begin
|
||||
// CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: error: cannot access member of interface `Core.IndexWith(type)` in type `str` that does not implement that interface [MissingImplInMemberAccess]
|
||||
// CHECK:STDERR: "Test"[C];
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
"Test"[C];
|
||||
//@dump-sem-ir-end
|
||||
}
|
||||
|
||||
// --- fail_bad_decl.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "string.at" [InvalidBuiltinSignature]
|
||||
// CHECK:STDERR: fn At(s: str, index: i64) -> i32 = "string.at";
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
fn At(s: str, index: i64) -> i32 = "string.at";
|
||||
|
||||
// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "string.at" [InvalidBuiltinSignature]
|
||||
// CHECK:STDERR: fn At2(s: str) -> char = "string.at";
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
fn At2(s: str) -> char = "string.at";
|
||||
|
||||
// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "string.at" [InvalidBuiltinSignature]
|
||||
// CHECK:STDERR: fn At3(s: i32, index: i64) -> char = "string.at";
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
fn At3(s: i32, index: i64) -> char = "string.at";
|
||||
|
||||
// --- test_string_indexing.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn TestStringIndexing() {
|
||||
//@dump-sem-ir-begin
|
||||
let c: char = "Test"[0];
|
||||
let d: char = "Test"[3];
|
||||
//@dump-sem-ir-end
|
||||
}
|
||||
|
||||
// CHECK:STDOUT: --- fail_wrong_type.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %str.ee0: type = class_type @String [concrete]
|
||||
// CHECK:STDOUT: %int_64: Core.IntLiteral = int_value 64 [concrete]
|
||||
// CHECK:STDOUT: %u64: type = class_type @UInt, @UInt(%int_64) [concrete]
|
||||
// CHECK:STDOUT: %char: type = class_type @Char [concrete]
|
||||
// CHECK:STDOUT: %ptr.fb0: type = ptr_type %char [concrete]
|
||||
// CHECK:STDOUT: %str.0a6: %ptr.fb0 = string_literal "Test" [concrete]
|
||||
// CHECK:STDOUT: %int_4: %u64 = int_value 4 [concrete]
|
||||
// CHECK:STDOUT: %String.val: %str.ee0 = struct_value (%str.0a6, %int_4) [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @TestWrongType() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: %str: %ptr.fb0 = string_literal "Test" [concrete = constants.%str.0a6]
|
||||
// CHECK:STDOUT: %int_4: %u64 = int_value 4 [concrete = constants.%int_4]
|
||||
// CHECK:STDOUT: %String.val: %str.ee0 = struct_value (%str, %int_4) [concrete = constants.%String.val]
|
||||
// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- test_string_indexing.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %char: type = class_type @Char [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.b09: type = pattern_type %char [concrete]
|
||||
// CHECK:STDOUT: %str.ee0: type = class_type @String [concrete]
|
||||
// CHECK:STDOUT: %int_64: Core.IntLiteral = int_value 64 [concrete]
|
||||
// CHECK:STDOUT: %u64: type = class_type @UInt, @UInt(%int_64) [concrete]
|
||||
// CHECK:STDOUT: %ptr.fb0: type = ptr_type %char [concrete]
|
||||
// CHECK:STDOUT: %str.0a6: %ptr.fb0 = string_literal "Test" [concrete]
|
||||
// CHECK:STDOUT: %int_4: %u64 = int_value 4 [concrete]
|
||||
// CHECK:STDOUT: %String.val: %str.ee0 = struct_value (%str.0a6, %int_4) [concrete]
|
||||
// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete]
|
||||
// CHECK:STDOUT: %IndexWith.type.8ab: type = facet_type <@IndexWith, @IndexWith(Core.IntLiteral)> [concrete]
|
||||
// CHECK:STDOUT: %IndexWith.At.type.1ab: type = fn_type @IndexWith.At, @IndexWith(Core.IntLiteral) [concrete]
|
||||
// CHECK:STDOUT: %i64: type = class_type @Int, @Int(%int_64) [concrete]
|
||||
// CHECK:STDOUT: %ImplicitAs.type.e50: type = facet_type <@ImplicitAs, @ImplicitAs(%i64)> [concrete]
|
||||
// CHECK:STDOUT: %T.e3e: %ImplicitAs.type.e50 = symbolic_binding T, 0 [symbolic]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.type.b0c: type = fn_type @str.as.IndexWith.impl.At, @str.as.IndexWith.impl(%T.e3e) [symbolic]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.fb3: %str.as.IndexWith.impl.At.type.b0c = struct_value () [symbolic]
|
||||
// CHECK:STDOUT: %ImplicitAs.impl_witness.93f: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.14f, @Core.IntLiteral.as.ImplicitAs.impl(%int_64) [concrete]
|
||||
// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.e50 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.93f) [concrete]
|
||||
// CHECK:STDOUT: %IndexWith.impl_witness.332: <witness> = impl_witness imports.%IndexWith.impl_witness_table, @str.as.IndexWith.impl(%ImplicitAs.facet) [concrete]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.type.ae7: type = fn_type @str.as.IndexWith.impl.At, @str.as.IndexWith.impl(%ImplicitAs.facet) [concrete]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.a59: %str.as.IndexWith.impl.At.type.ae7 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %IndexWith.facet: %IndexWith.type.8ab = facet_value %str.ee0, (%IndexWith.impl_witness.332) [concrete]
|
||||
// CHECK:STDOUT: %.53d: type = fn_type_with_self_type %IndexWith.At.type.1ab, %IndexWith.facet [concrete]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.bound: <bound method> = bound_method %String.val, %str.as.IndexWith.impl.At.a59 [concrete]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.specific_fn: <specific function> = specific_function %str.as.IndexWith.impl.At.a59, @str.as.IndexWith.impl.At(%ImplicitAs.facet) [concrete]
|
||||
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %String.val, %str.as.IndexWith.impl.At.specific_fn [concrete]
|
||||
// CHECK:STDOUT: %int_84: %char = int_value 84 [concrete]
|
||||
// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete]
|
||||
// CHECK:STDOUT: %int_116: %char = int_value 116 [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
// CHECK:STDOUT: %Core.import_ref.484 = import_ref Core//prelude/types/string, loc{{\d+_\d+}}, unloaded
|
||||
// CHECK:STDOUT: %Core.import_ref.d4b: @str.as.IndexWith.impl.%str.as.IndexWith.impl.At.type (%str.as.IndexWith.impl.At.type.b0c) = import_ref Core//prelude/types/string, loc{{\d+_\d+}}, loaded [symbolic = @str.as.IndexWith.impl.%str.as.IndexWith.impl.At (constants.%str.as.IndexWith.impl.At.fb3)]
|
||||
// CHECK:STDOUT: %IndexWith.impl_witness_table = impl_witness_table (%Core.import_ref.484, %Core.import_ref.d4b), @str.as.IndexWith.impl [concrete]
|
||||
// CHECK:STDOUT: %Core.import_ref.027 = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, unloaded
|
||||
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.14f = impl_witness_table (%Core.import_ref.027), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @TestStringIndexing() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %c.patt: %pattern_type.b09 = value_binding_pattern c [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %str.loc5: %ptr.fb0 = string_literal "Test" [concrete = constants.%str.0a6]
|
||||
// CHECK:STDOUT: %int_4.loc5: %u64 = int_value 4 [concrete = constants.%int_4]
|
||||
// CHECK:STDOUT: %String.val.loc5: %str.ee0 = struct_value (%str.loc5, %int_4.loc5) [concrete = constants.%String.val]
|
||||
// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0]
|
||||
// CHECK:STDOUT: %impl.elem1.loc5: %.53d = impl_witness_access constants.%IndexWith.impl_witness.332, element1 [concrete = constants.%str.as.IndexWith.impl.At.a59]
|
||||
// CHECK:STDOUT: %bound_method.loc5_25.1: <bound method> = bound_method %String.val.loc5, %impl.elem1.loc5 [concrete = constants.%str.as.IndexWith.impl.At.bound]
|
||||
// CHECK:STDOUT: %ImplicitAs.facet.loc5_25.1: %ImplicitAs.type.e50 = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness.93f) [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %.loc5_25.1: %ImplicitAs.type.e50 = converted Core.IntLiteral, %ImplicitAs.facet.loc5_25.1 [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %ImplicitAs.facet.loc5_25.2: %ImplicitAs.type.e50 = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness.93f) [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %.loc5_25.2: %ImplicitAs.type.e50 = converted Core.IntLiteral, %ImplicitAs.facet.loc5_25.2 [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %specific_fn.loc5: <specific function> = specific_function %impl.elem1.loc5, @str.as.IndexWith.impl.At(constants.%ImplicitAs.facet) [concrete = constants.%str.as.IndexWith.impl.At.specific_fn]
|
||||
// CHECK:STDOUT: %bound_method.loc5_25.2: <bound method> = bound_method %String.val.loc5, %specific_fn.loc5 [concrete = constants.%bound_method]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.loc5: init %char = call %bound_method.loc5_25.2(%String.val.loc5, %int_0) [concrete = constants.%int_84]
|
||||
// CHECK:STDOUT: %.loc5_25.3: %char = value_of_initializer %str.as.IndexWith.impl.At.call.loc5 [concrete = constants.%int_84]
|
||||
// CHECK:STDOUT: %.loc5_25.4: %char = converted %str.as.IndexWith.impl.At.call.loc5, %.loc5_25.3 [concrete = constants.%int_84]
|
||||
// CHECK:STDOUT: %c: %char = value_binding c, %.loc5_25.4
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %d.patt: %pattern_type.b09 = value_binding_pattern d [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %str.loc6: %ptr.fb0 = string_literal "Test" [concrete = constants.%str.0a6]
|
||||
// CHECK:STDOUT: %int_4.loc6: %u64 = int_value 4 [concrete = constants.%int_4]
|
||||
// CHECK:STDOUT: %String.val.loc6: %str.ee0 = struct_value (%str.loc6, %int_4.loc6) [concrete = constants.%String.val]
|
||||
// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete = constants.%int_3]
|
||||
// CHECK:STDOUT: %impl.elem1.loc6: %.53d = impl_witness_access constants.%IndexWith.impl_witness.332, element1 [concrete = constants.%str.as.IndexWith.impl.At.a59]
|
||||
// CHECK:STDOUT: %bound_method.loc6_25.1: <bound method> = bound_method %String.val.loc6, %impl.elem1.loc6 [concrete = constants.%str.as.IndexWith.impl.At.bound]
|
||||
// CHECK:STDOUT: %ImplicitAs.facet.loc6_25.1: %ImplicitAs.type.e50 = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness.93f) [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %.loc6_25.1: %ImplicitAs.type.e50 = converted Core.IntLiteral, %ImplicitAs.facet.loc6_25.1 [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %ImplicitAs.facet.loc6_25.2: %ImplicitAs.type.e50 = facet_value Core.IntLiteral, (constants.%ImplicitAs.impl_witness.93f) [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %.loc6_25.2: %ImplicitAs.type.e50 = converted Core.IntLiteral, %ImplicitAs.facet.loc6_25.2 [concrete = constants.%ImplicitAs.facet]
|
||||
// CHECK:STDOUT: %specific_fn.loc6: <specific function> = specific_function %impl.elem1.loc6, @str.as.IndexWith.impl.At(constants.%ImplicitAs.facet) [concrete = constants.%str.as.IndexWith.impl.At.specific_fn]
|
||||
// CHECK:STDOUT: %bound_method.loc6_25.2: <bound method> = bound_method %String.val.loc6, %specific_fn.loc6 [concrete = constants.%bound_method]
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.loc6: init %char = call %bound_method.loc6_25.2(%String.val.loc6, %int_3) [concrete = constants.%int_116]
|
||||
// CHECK:STDOUT: %.loc6_25.3: %char = value_of_initializer %str.as.IndexWith.impl.At.call.loc6 [concrete = constants.%int_116]
|
||||
// CHECK:STDOUT: %.loc6_25.4: %char = converted %str.as.IndexWith.impl.At.call.loc6, %.loc6_25.3 [concrete = constants.%int_116]
|
||||
// CHECK:STDOUT: %d: %char = value_binding d, %.loc6_25.4
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -452,6 +452,8 @@ CARBON_DIAGNOSTIC_KIND(NegativeIntInUnsignedType)
|
||||
CARBON_DIAGNOSTIC_KIND(NonConstantCallToCompTimeOnlyFunction)
|
||||
CARBON_DIAGNOSTIC_KIND(CompTimeOnlyFunctionHere)
|
||||
CARBON_DIAGNOSTIC_KIND(SelfOutsideImplicitParamList)
|
||||
CARBON_DIAGNOSTIC_KIND(StringAtIndexOutOfBounds)
|
||||
CARBON_DIAGNOSTIC_KIND(StringAtIndexNegative)
|
||||
CARBON_DIAGNOSTIC_KIND(StringLiteralTooLong)
|
||||
CARBON_DIAGNOSTIC_KIND(StringLiteralTypeIncomplete)
|
||||
CARBON_DIAGNOSTIC_KIND(StringLiteralTypeUnexpected)
|
||||
|
||||
@@ -315,6 +315,34 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
|
||||
return;
|
||||
}
|
||||
|
||||
case SemIR::BuiltinFunctionKind::StringAt: {
|
||||
auto string_inst_id = arg_ids[0];
|
||||
auto* string_arg = context.GetValue(string_inst_id);
|
||||
|
||||
auto string_type_id = context.GetTypeIdOfInst(string_inst_id);
|
||||
auto* string_type = context.GetType(string_type_id);
|
||||
auto* string_value =
|
||||
context.builder().CreateLoad(string_type, string_arg, "string.load");
|
||||
|
||||
auto* string_ptr_field =
|
||||
context.builder().CreateExtractValue(string_value, {0}, "string.ptr");
|
||||
|
||||
auto* index_value = context.GetValue(arg_ids[1]);
|
||||
|
||||
auto* char_ptr = context.builder().CreateInBoundsGEP(
|
||||
llvm::Type::getInt8Ty(context.llvm_context()), string_ptr_field,
|
||||
index_value, "string.char_ptr");
|
||||
|
||||
auto* char_i8 = context.builder().CreateLoad(
|
||||
llvm::Type::getInt8Ty(context.llvm_context()), char_ptr,
|
||||
"string.char");
|
||||
|
||||
context.SetLocal(inst_id, context.builder().CreateZExt(
|
||||
char_i8, context.GetTypeOfInst(inst_id),
|
||||
"string.char.zext"));
|
||||
return;
|
||||
}
|
||||
|
||||
case SemIR::BuiltinFunctionKind::TypeAnd: {
|
||||
context.SetLocal(inst_id, context.GetTypeAsValue());
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// TIP: To test this file alone, run:
|
||||
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/operators/string_indexing.carbon
|
||||
// TIP: To dump output, run:
|
||||
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/operators/string_indexing.carbon
|
||||
|
||||
fn F() -> char {
|
||||
let c: char = "Test"[0];
|
||||
return c;
|
||||
}
|
||||
|
||||
fn F2(index: i64) -> char {
|
||||
let c: char = "Test"[index];
|
||||
return c;
|
||||
}
|
||||
|
||||
// CHECK:STDOUT: ; ModuleID = 'string_indexing.carbon'
|
||||
// CHECK:STDOUT: source_filename = "string_indexing.carbon"
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: @0 = private unnamed_addr constant [5 x i8] c"Test\00", align 1
|
||||
// CHECK:STDOUT: @String.val.String.val = internal constant { ptr, i64 } { ptr @0, i64 4 }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: ; Function Attrs: nounwind
|
||||
// CHECK:STDOUT: define i8 @_CF.Main() #0 !dbg !4 {
|
||||
// CHECK:STDOUT: entry:
|
||||
// CHECK:STDOUT: ret i8 84, !dbg !8
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: ; Function Attrs: nounwind
|
||||
// CHECK:STDOUT: define i8 @_CF2.Main(i64 %index) #0 !dbg !9 {
|
||||
// CHECK:STDOUT: entry:
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.string.load = load { ptr, i64 }, ptr @String.val.String.val, align 8, !dbg !15
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.string.ptr = extractvalue { ptr, i64 } %str.as.IndexWith.impl.At.call.string.load, 0, !dbg !15
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.string.char_ptr = getelementptr inbounds i8, ptr %str.as.IndexWith.impl.At.call.string.ptr, i64 %index, !dbg !15
|
||||
// CHECK:STDOUT: %str.as.IndexWith.impl.At.call.string.char = load i8, ptr %str.as.IndexWith.impl.At.call.string.char_ptr, align 1, !dbg !15
|
||||
// CHECK:STDOUT: ret i8 %str.as.IndexWith.impl.At.call.string.char, !dbg !16
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: attributes #0 = { nounwind }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
|
||||
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
|
||||
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
|
||||
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
|
||||
// CHECK:STDOUT: !3 = !DIFile(filename: "string_indexing.carbon", directory: "")
|
||||
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
|
||||
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
|
||||
// CHECK:STDOUT: !6 = !{!7}
|
||||
// CHECK:STDOUT: !7 = !DIBasicType(name: "int", size: 8, encoding: DW_ATE_unsigned)
|
||||
// CHECK:STDOUT: !8 = !DILocation(line: 15, column: 5, scope: !4)
|
||||
// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "F2", linkageName: "_CF2.Main", scope: null, file: !3, line: 18, type: !10, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !13)
|
||||
// CHECK:STDOUT: !10 = !DISubroutineType(types: !11)
|
||||
// CHECK:STDOUT: !11 = !{!7, !12}
|
||||
// CHECK:STDOUT: !12 = !DIBasicType(name: "int", size: 64, encoding: DW_ATE_signed)
|
||||
// CHECK:STDOUT: !13 = !{!14}
|
||||
// CHECK:STDOUT: !14 = !DILocalVariable(arg: 1, scope: !9, type: !12)
|
||||
// CHECK:STDOUT: !15 = !DILocation(line: 19, column: 19, scope: !9)
|
||||
// CHECK:STDOUT: !16 = !DILocation(line: 20, column: 5, scope: !9)
|
||||
@@ -192,6 +192,36 @@ struct AnyType {
|
||||
}
|
||||
};
|
||||
|
||||
// Constraint that checks if a type is Core.String.
|
||||
struct CoreStringType {
|
||||
static auto CheckType(const File& sem_ir, ValidateState& /*state*/,
|
||||
TypeId type_id) -> bool {
|
||||
auto type_inst_id = sem_ir.types().GetInstId(type_id);
|
||||
auto class_type = sem_ir.insts().TryGetAs<ClassType>(type_inst_id);
|
||||
if (!class_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& class_info = sem_ir.classes().Get(class_type->class_id);
|
||||
return sem_ir.names().GetFormatted(class_info.name_id).str() == "String";
|
||||
}
|
||||
};
|
||||
|
||||
// Constraint that checks if a type is Core.Char.
|
||||
struct CoreCharType {
|
||||
static auto CheckType(const File& sem_ir, ValidateState& /*state*/,
|
||||
TypeId type_id) -> bool {
|
||||
auto type_inst_id = sem_ir.types().GetInstId(type_id);
|
||||
auto class_type = sem_ir.insts().TryGetAs<ClassType>(type_inst_id);
|
||||
if (!class_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& class_info = sem_ir.classes().Get(class_type->class_id);
|
||||
return sem_ir.names().GetFormatted(class_info.name_id).str() == "Char";
|
||||
}
|
||||
};
|
||||
|
||||
// Constraint that requires the type to be the type type.
|
||||
using Type = BuiltinType<TypeType::TypeInstId>;
|
||||
|
||||
@@ -373,6 +403,11 @@ constexpr BuiltinInfo PrintInt = {
|
||||
constexpr BuiltinInfo ReadChar = {"read.char",
|
||||
ValidateSignature<auto()->AnySizedInt>};
|
||||
|
||||
// Gets a character from a string at the given index.
|
||||
constexpr BuiltinInfo StringAt = {
|
||||
"string.at",
|
||||
ValidateSignature<auto(CoreStringType, AnyType)->CoreCharType>};
|
||||
|
||||
// Returns the `Core.CharLiteral` type.
|
||||
constexpr BuiltinInfo CharLiteralMakeType = {"char_literal.make_type",
|
||||
ValidateSignature<auto()->Type>};
|
||||
|
||||
@@ -30,6 +30,7 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrimitiveCopy)
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintChar)
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintInt)
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(ReadChar)
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(StringAt)
|
||||
|
||||
// Type factories.
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(CharLiteralMakeType)
|
||||
|
||||
Reference in New Issue
Block a user