Skip to content

Commit 89eed42

Browse files
Expose indexing as a language interface (#4370)
This PR makes it so that types can implement the `IndexWith` interface so that they can provide their custom indexing behavior. --------- Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent c30b1d1 commit 89eed42

File tree

464 files changed

+2506
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

464 files changed

+2506
-29
lines changed

core/prelude/operators.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ package Core library "prelude/operators";
66

77
export import library "prelude/operators/arithmetic";
88
export import library "prelude/operators/as";
9+
export import library "prelude/operators/index";
910
export import library "prelude/operators/bitwise";
1011
export import library "prelude/operators/comparison";
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
5+
package Core library "prelude/operators/index";
6+
7+
interface IndexWith(SubscriptType:! type, ElementType:! type) {
8+
fn At[self: Self](subscript: SubscriptType) -> ElementType;
9+
}

toolchain/check/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ cc_library(
126126
"//toolchain/parse:node_kind",
127127
"//toolchain/parse:tree",
128128
"//toolchain/parse:tree_node_diagnostic_converter",
129+
"//toolchain/sem_ir:builtin_inst_kind",
129130
"//toolchain/sem_ir:entry_point",
130131
"//toolchain/sem_ir:file",
131132
"//toolchain/sem_ir:ids",

toolchain/check/handle_index.cpp

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
// Exceptions. See /LICENSE for license information.
33
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44

5+
#include <optional>
6+
57
#include "toolchain/base/kind_switch.h"
68
#include "toolchain/check/context.h"
79
#include "toolchain/check/convert.h"
810
#include "toolchain/check/handle.h"
11+
#include "toolchain/check/operator.h"
12+
#include "toolchain/diagnostics/diagnostic.h"
13+
#include "toolchain/sem_ir/builtin_inst_kind.h"
914
#include "toolchain/sem_ir/inst.h"
15+
#include "toolchain/sem_ir/typed_insts.h"
1016

1117
namespace Carbon::Check {
1218

@@ -16,17 +22,113 @@ auto HandleParseNode(Context& /*context*/, Parse::IndexExprStartId /*node_id*/)
1622
return true;
1723
}
1824

25+
// Returns the argument values of the `IndexWith` interface. Arguments
26+
// correspond to the `SubscriptType` and the `ElementType`. If no arguments are
27+
// used to define `IndexWith`, this returns an empty array reference. If the
28+
// class does not implement the said interface, this returns a `std::nullopt`.
29+
// TODO: Switch to using an associated type instead of a parameter for the
30+
// `ElementType`.
31+
static auto GetIndexWithArgs(Context& context, Parse::NodeId node_id,
32+
SemIR::TypeId self_id)
33+
-> std::optional<llvm::ArrayRef<SemIR::InstId>> {
34+
auto index_with_inst_id = context.LookupNameInCore(node_id, "IndexWith");
35+
// If the `IndexWith` interface doesn't have generic arguments then return an
36+
// empty reference.
37+
if (context.insts().Is<SemIR::InterfaceType>(index_with_inst_id)) {
38+
return llvm::ArrayRef<SemIR::InstId>();
39+
}
40+
41+
auto index_with_inst =
42+
context.insts().TryGetAsIfValid<SemIR::StructValue>(index_with_inst_id);
43+
if (!index_with_inst) {
44+
return std::nullopt;
45+
}
46+
47+
auto index_with_interface =
48+
context.types().TryGetAs<SemIR::GenericInterfaceType>(
49+
index_with_inst->type_id);
50+
if (!index_with_interface) {
51+
return std::nullopt;
52+
}
53+
54+
for (const auto& impl : context.impls().array_ref()) {
55+
auto impl_self_type_id = context.GetTypeIdForTypeInst(impl.self_id);
56+
auto impl_constraint_type_id =
57+
context.GetTypeIdForTypeInst(impl.constraint_id);
58+
59+
if (impl_self_type_id != self_id) {
60+
continue;
61+
}
62+
auto interface_type =
63+
context.types().TryGetAs<SemIR::InterfaceType>(impl_constraint_type_id);
64+
if (!interface_type) {
65+
continue;
66+
}
67+
68+
if (index_with_interface->interface_id != interface_type->interface_id) {
69+
continue;
70+
}
71+
72+
return context.inst_blocks().GetOrEmpty(
73+
context.specifics().Get(interface_type->specific_id).args_id);
74+
}
75+
76+
return std::nullopt;
77+
}
78+
79+
// Performs an index with base expression `operand_inst_id` and
80+
// `operand_type_id` for types that are not an array. This checks if
81+
// the base expression implements the `IndexWith` interface; if so, uses the
82+
// `At` associative method, otherwise prints a diagnostic.
83+
static auto PerformIndexWith(Context& context, Parse::NodeId node_id,
84+
SemIR::InstId operand_inst_id,
85+
SemIR::TypeId operand_type_id,
86+
SemIR::InstId index_inst_id) -> SemIR::InstId {
87+
auto args = GetIndexWithArgs(context, node_id, operand_type_id);
88+
89+
// If the type does not implement the `IndexWith` interface, then return
90+
// an error.
91+
if (!args) {
92+
CARBON_DIAGNOSTIC(TypeNotIndexable, Error,
93+
"type {0} does not support indexing", SemIR::TypeId);
94+
context.emitter().Emit(node_id, TypeNotIndexable, operand_type_id);
95+
return SemIR::InstId::BuiltinError;
96+
}
97+
98+
Operator op{
99+
.interface_name = "IndexWith",
100+
.interface_args_ref = *args,
101+
.op_name = "At",
102+
};
103+
104+
// IndexWith is defined without generic arguments.
105+
if (args->empty()) {
106+
return BuildBinaryOperator(context, node_id, op, operand_inst_id,
107+
index_inst_id);
108+
}
109+
110+
// The first argument of the `IndexWith` interface corresponds to the
111+
// `SubscriptType`, so first cast `index_inst_id` to that type.
112+
auto subscript_type_id = context.GetTypeIdForTypeInst((*args)[0]);
113+
auto cast_index_id =
114+
ConvertToValueOfType(context, node_id, index_inst_id, subscript_type_id);
115+
116+
return BuildBinaryOperator(context, node_id, op, operand_inst_id,
117+
cast_index_id);
118+
}
119+
19120
auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
20121
auto index_inst_id = context.node_stack().PopExpr();
21122
auto operand_inst_id = context.node_stack().PopExpr();
22123
operand_inst_id = ConvertToValueOrRefExpr(context, operand_inst_id);
23124
auto operand_inst = context.insts().Get(operand_inst_id);
24125
auto operand_type_id = operand_inst.type_id();
126+
25127
CARBON_KIND_SWITCH(context.types().GetAsInst(operand_type_id)) {
26128
case CARBON_KIND(SemIR::ArrayType array_type): {
27-
auto index_node_id = context.insts().GetLocId(index_inst_id);
129+
auto index_loc_id = context.insts().GetLocId(index_inst_id);
28130
auto cast_index_id = ConvertToValueOfType(
29-
context, index_node_id, index_inst_id,
131+
context, index_loc_id, index_inst_id,
30132
context.GetBuiltinType(SemIR::BuiltinInstKind::IntType));
31133
auto array_cat =
32134
SemIR::GetExprCategory(context.sem_ir(), operand_inst_id);
@@ -52,13 +154,14 @@ auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
52154
context.node_stack().Push(node_id, elem_id);
53155
return true;
54156
}
157+
55158
default: {
159+
auto elem_id = SemIR::InstId::BuiltinError;
56160
if (operand_type_id != SemIR::TypeId::Error) {
57-
CARBON_DIAGNOSTIC(TypeNotIndexable, Error,
58-
"type {0} does not support indexing", TypeOfInstId);
59-
context.emitter().Emit(node_id, TypeNotIndexable, operand_inst_id);
161+
elem_id = PerformIndexWith(context, node_id, operand_inst_id,
162+
operand_type_id, index_inst_id);
60163
}
61-
context.node_stack().Push(node_id, SemIR::InstId::BuiltinError);
164+
context.node_stack().Push(node_id, elem_id);
62165
return true;
63166
}
64167
}

toolchain/check/testdata/alias/fail_bool_value.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ let a_test: bool = a;
3131
// CHECK:STDOUT: import Core//prelude/types
3232
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
3333
// CHECK:STDOUT: import Core//prelude/operators/as
34+
// CHECK:STDOUT: import Core//prelude/operators/index
3435
// CHECK:STDOUT: import Core//prelude/operators/bitwise
3536
// CHECK:STDOUT: import Core//prelude/operators/comparison
3637
// CHECK:STDOUT: import Core//prelude/types/bool

toolchain/check/testdata/alias/fail_builtins.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ alias b = bool;
3838
// CHECK:STDOUT: import Core//prelude/types
3939
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
4040
// CHECK:STDOUT: import Core//prelude/operators/as
41+
// CHECK:STDOUT: import Core//prelude/operators/index
4142
// CHECK:STDOUT: import Core//prelude/operators/bitwise
4243
// CHECK:STDOUT: import Core//prelude/operators/comparison
4344
// CHECK:STDOUT: import Core//prelude/types/bool

toolchain/check/testdata/alias/fail_control_flow.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ alias a = true or false;
3535
// CHECK:STDOUT: import Core//prelude/types
3636
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
3737
// CHECK:STDOUT: import Core//prelude/operators/as
38+
// CHECK:STDOUT: import Core//prelude/operators/index
3839
// CHECK:STDOUT: import Core//prelude/operators/bitwise
3940
// CHECK:STDOUT: import Core//prelude/operators/comparison
4041
// CHECK:STDOUT: import Core//prelude/types/bool

toolchain/check/testdata/array/array_in_place.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ fn G() {
4343
// CHECK:STDOUT: import Core//prelude/types
4444
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
4545
// CHECK:STDOUT: import Core//prelude/operators/as
46+
// CHECK:STDOUT: import Core//prelude/operators/index
4647
// CHECK:STDOUT: import Core//prelude/operators/bitwise
4748
// CHECK:STDOUT: import Core//prelude/operators/comparison
4849
// CHECK:STDOUT: import Core//prelude/types/bool

toolchain/check/testdata/array/array_vs_tuple.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ fn G() {
4343
// CHECK:STDOUT: import Core//prelude/types
4444
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
4545
// CHECK:STDOUT: import Core//prelude/operators/as
46+
// CHECK:STDOUT: import Core//prelude/operators/index
4647
// CHECK:STDOUT: import Core//prelude/operators/bitwise
4748
// CHECK:STDOUT: import Core//prelude/operators/comparison
4849
// CHECK:STDOUT: import Core//prelude/types/bool

toolchain/check/testdata/array/assign_return_value.carbon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ fn Run() {
4141
// CHECK:STDOUT: import Core//prelude/types
4242
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
4343
// CHECK:STDOUT: import Core//prelude/operators/as
44+
// CHECK:STDOUT: import Core//prelude/operators/index
4445
// CHECK:STDOUT: import Core//prelude/operators/bitwise
4546
// CHECK:STDOUT: import Core//prelude/operators/comparison
4647
// CHECK:STDOUT: import Core//prelude/types/bool

0 commit comments

Comments
 (0)