Skip to content

Commit

Permalink
where check stage, step 3: some type checking (#4364)
Browse files Browse the repository at this point in the history
With this, we now check:

* The left argument to `where` is a facet type
* The right argument of a rewrite (`=`) requirement converts to the type
of the left argument.
* The left argument of an `impls` requirement is a type and the right
argument is a facet type.

No checking is done for `==` constraints yet.

In addition, make the "is facet type" query into its own function and
fix some comments noticed as part of this change.

This change reveals that accessing the members of a facet, like `.Self`,
isn't doing the right thing, and will have to be fixed in a follow-on
PR. Some tests have been adjusted or disabled as a result.

---------

Co-authored-by: Josh L <[email protected]>
  • Loading branch information
josh11b and josh11b authored Oct 5, 2024
1 parent 17411c5 commit 6dbeda6
Show file tree
Hide file tree
Showing 10 changed files with 1,286 additions and 160 deletions.
4 changes: 1 addition & 3 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1192,9 +1192,7 @@ auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
auto type_id =
insts().Get(constant_values().GetInstId(constant_id)).type_id();
// TODO: For now, we allow values of facet type to be used as types.
CARBON_CHECK(type_id == SemIR::TypeId::TypeType ||
types().Is<SemIR::InterfaceType>(type_id) ||
constant_id == SemIR::ConstantId::Error,
CARBON_CHECK(IsFacetType(type_id) || constant_id == SemIR::ConstantId::Error,
"Forming type ID for non-type constant of type {0}",
types().GetAsInst(type_id));

Expand Down
6 changes: 6 additions & 0 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ class Context {
: SemIR::TypeId::Error;
}

// Returns whether `type_id` represents a facet type.
auto IsFacetType(SemIR::TypeId type_id) -> bool {
return type_id == SemIR::TypeId::TypeType ||
types().Is<SemIR::InterfaceType>(type_id);
}

// TODO: Consider moving these `Get*Type` functions to a separate class.

// Gets the type for the name of an associated entity.
Expand Down
4 changes: 2 additions & 2 deletions toolchain/check/convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ namespace Carbon::Check {
// Description of the target of a conversion.
struct ConversionTarget {
enum Kind : int8_t {
// Convert to a value of type `type`.
// Convert to a value of type `type_id`.
Value,
// Convert to either a value or a reference of type `type`.
// Convert to either a value or a reference of type `type_id`.
ValueOrRef,
// Convert for an explicit `as` cast. This allows any expression category
// as the result, and uses the `As` interface instead of the `ImplicitAs`
Expand Down
40 changes: 30 additions & 10 deletions toolchain/check/handle_where.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
// `MyInterface where .Member = i32`.
auto [self_node, self_id] = context.node_stack().PopExprWithNodeId();
auto self_type_id = ExprAsType(context, self_node, self_id).type_id;
// TODO: Validate that `self_type_id` represents a facet type. Only facet
// types may have `where` restrictions.
// Only facet types may have `where` restrictions.
if (!context.IsFacetType(self_type_id)) {
CARBON_DIAGNOSTIC(WhereOnNonFacetType, Error,
"left argument of `where` operator must be a facet type");
context.emitter().Emit(self_node, WhereOnNonFacetType);
self_type_id = SemIR::TypeId::Error;
}

// Introduce a name scope so that we can remove the `.Self` entry we are
// adding to name lookup at the end of the `where` expression.
Expand Down Expand Up @@ -55,22 +60,25 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {

auto HandleParseNode(Context& context, Parse::RequirementEqualId node_id)
-> bool {
auto rhs = context.node_stack().PopExpr();
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto lhs = context.node_stack().PopExpr();
// TODO: convert rhs to type of lhs

// Convert rhs to type of lhs.
SemIR::InstId rhs_inst_id = ConvertToValueOfType(
context, rhs_node, rhs_id, context.insts().Get(lhs).type_id());

// Build up the list of arguments for the `WhereExpr` inst.
context.args_type_info_stack().AddInstId(
context.AddInstInNoBlock<SemIR::RequirementRewrite>(
node_id, {.lhs_id = lhs, .rhs_id = rhs}));
node_id, {.lhs_id = lhs, .rhs_id = rhs_inst_id}));
return true;
}

auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)
-> bool {
auto rhs = context.node_stack().PopExpr();
auto lhs = context.node_stack().PopExpr();
// TODO: type check lhs and rhs are compatible
// TODO: type check lhs and rhs are comparable

// Build up the list of arguments for the `WhereExpr` inst.
context.args_type_info_stack().AddInstId(
Expand All @@ -81,14 +89,26 @@ auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)

auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
-> bool {
auto rhs = context.node_stack().PopExpr();
auto lhs = context.node_stack().PopExpr();
// TODO: check lhs is a facet and rhs is a facet type
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();

// Check lhs is a facet and rhs is a facet type.
auto lhs_as_type = ExprAsType(context, lhs_node, lhs_id);
auto rhs_as_type = ExprAsType(context, rhs_node, rhs_id);
if (rhs_as_type.type_id != SemIR::TypeId::Error &&
!context.IsFacetType(rhs_as_type.type_id)) {
CARBON_DIAGNOSTIC(
ImplsOnNonFacetType, Error,
"right argument of `impls` requirement must be a facet type");
context.emitter().Emit(rhs_node, ImplsOnNonFacetType);
rhs_as_type.inst_id = SemIR::InstId::BuiltinError;
}

// Build up the list of arguments for the `WhereExpr` inst.
context.args_type_info_stack().AddInstId(
context.AddInstInNoBlock<SemIR::RequirementImpls>(
node_id, {.lhs_id = lhs, .rhs_id = rhs}));
node_id,
{.lhs_id = lhs_as_type.inst_id, .rhs_id = rhs_as_type.inst_id}));
return true;
}

Expand Down
117 changes: 105 additions & 12 deletions toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,51 @@

interface I { let T:! type; }

// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:1: error: semantics TODO: `impl of interface with associated constant`
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+10]]:1: error: semantics TODO: `impl of interface with associated constant`
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+6]]:27: error: cannot implicitly convert from `type` to `<associated type in I>`
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:27: note: type `type` does not implement interface `ImplicitAs`
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~
impl bool as I where .T = bool {}

// CHECK:STDOUT: --- fail_todo_impl_assoc_const.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %I.type: type = interface_type @I [template]
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Self.1: %I.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %.1: type = assoc_entity_type %I.type, type [template]
// CHECK:STDOUT: %.2: %.1 = assoc_entity element0, @I.%T [template]
// CHECK:STDOUT: %Bool.type: type = fn_type @Bool [template]
// CHECK:STDOUT: %.3: type = tuple_type () [template]
// CHECK:STDOUT: %Bool: %Bool.type = struct_value () [template]
// CHECK:STDOUT: %.Self: %I.type = bind_symbolic_name .Self, 0 [symbolic]
// CHECK:STDOUT: %ImplicitAs.type.1: type = generic_interface_type @ImplicitAs [template]
// CHECK:STDOUT: %ImplicitAs: %ImplicitAs.type.1 = struct_value () [template]
// CHECK:STDOUT: %Dest: type = bind_symbolic_name Dest, 0 [symbolic]
// CHECK:STDOUT: %ImplicitAs.type.2: type = interface_type @ImplicitAs, @ImplicitAs(%Dest) [symbolic]
// CHECK:STDOUT: %Self.2: @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2) = bind_symbolic_name Self, 1 [symbolic]
// CHECK:STDOUT: %Self.3: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic]
// CHECK:STDOUT: %Convert.type.1: type = fn_type @Convert, @ImplicitAs(%Dest) [symbolic]
// CHECK:STDOUT: %Convert.1: %Convert.type.1 = struct_value () [symbolic]
// CHECK:STDOUT: %.4: type = assoc_entity_type %ImplicitAs.type.2, %Convert.type.1 [symbolic]
// CHECK:STDOUT: %.5: %.4 = assoc_entity element0, imports.%import_ref.6 [symbolic]
// CHECK:STDOUT: %ImplicitAs.type.3: type = interface_type @ImplicitAs, @ImplicitAs(%.1) [template]
// CHECK:STDOUT: %Convert.type.2: type = fn_type @Convert, @ImplicitAs(%.1) [template]
// CHECK:STDOUT: %Convert.2: %Convert.type.2 = struct_value () [template]
// CHECK:STDOUT: %.6: type = assoc_entity_type %ImplicitAs.type.3, %Convert.type.2 [template]
// CHECK:STDOUT: %.7: %.6 = assoc_entity element0, imports.%import_ref.6 [template]
// CHECK:STDOUT: %.8: %.4 = assoc_entity element0, imports.%import_ref.7 [symbolic]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Bool = %import_ref
// CHECK:STDOUT: .Bool = %import_ref.1
// CHECK:STDOUT: .ImplicitAs = %import_ref.2
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/operators
// CHECK:STDOUT: import Core//prelude/types
Expand All @@ -40,7 +64,13 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: import Core//prelude/operators/comparison
// CHECK:STDOUT: import Core//prelude/types/bool
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref: %Bool.type = import_ref Core//prelude/types/bool, inst+2, loaded [template = constants.%Bool]
// CHECK:STDOUT: %import_ref.1: %Bool.type = import_ref Core//prelude/types/bool, inst+2, loaded [template = constants.%Bool]
// CHECK:STDOUT: %import_ref.2: %ImplicitAs.type.1 = import_ref Core//prelude/operators/as, inst+40, loaded [template = constants.%ImplicitAs]
// CHECK:STDOUT: %import_ref.3 = import_ref Core//prelude/operators/as, inst+45, unloaded
// CHECK:STDOUT: %import_ref.4: @ImplicitAs.%.1 (%.4) = import_ref Core//prelude/operators/as, inst+63, loaded [symbolic = @ImplicitAs.%.2 (constants.%.8)]
// CHECK:STDOUT: %import_ref.5 = import_ref Core//prelude/operators/as, inst+56, unloaded
// CHECK:STDOUT: %import_ref.6 = import_ref Core//prelude/operators/as, inst+56, unloaded
// CHECK:STDOUT: %import_ref.7 = import_ref Core//prelude/operators/as, inst+56, unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
Expand All @@ -51,22 +81,28 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
// CHECK:STDOUT: impl_decl @impl [template] {} {
// CHECK:STDOUT: %bool.make_type.loc16_6: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc16_6.1: type = value_of_initializer %bool.make_type.loc16_6 [template = bool]
// CHECK:STDOUT: %.loc16_6.2: type = converted %bool.make_type.loc16_6, %.loc16_6.1 [template = bool]
// CHECK:STDOUT: %bool.make_type.loc23_6: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc23_6.1: type = value_of_initializer %bool.make_type.loc23_6 [template = bool]
// CHECK:STDOUT: %.loc23_6.2: type = converted %bool.make_type.loc23_6, %.loc23_6.1 [template = bool]
// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
// CHECK:STDOUT: %.Self: %I.type = bind_symbolic_name .Self, 0 [symbolic = constants.%.Self]
// CHECK:STDOUT: %.Self.ref: %I.type = name_ref .Self, %.Self [symbolic = constants.%.Self]
// CHECK:STDOUT: %T.ref: %.1 = name_ref T, @I.%.loc11 [template = constants.%.2]
// CHECK:STDOUT: %bool.make_type.loc16_27: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc16_16: type = where_expr %.Self [template = constants.%I.type] {
// CHECK:STDOUT: requirement_rewrite %T.ref, %bool.make_type.loc16_27
// CHECK:STDOUT: %bool.make_type.loc23_27: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %ImplicitAs.type: type = interface_type @ImplicitAs, @ImplicitAs(constants.%.1) [template = constants.%ImplicitAs.type.3]
// CHECK:STDOUT: %.loc23_27.1: %.6 = specific_constant imports.%import_ref.4, @ImplicitAs(constants.%.1) [template = constants.%.7]
// CHECK:STDOUT: %Convert.ref: %.6 = name_ref Convert, %.loc23_27.1 [template = constants.%.7]
// CHECK:STDOUT: %.loc23_27.2: ref type = temporary_storage
// CHECK:STDOUT: %.loc23_27.3: ref type = temporary %.loc23_27.2, %bool.make_type.loc23_27
// CHECK:STDOUT: %.loc23_27.4: %.1 = converted %bool.make_type.loc23_27, <error> [template = <error>]
// CHECK:STDOUT: %.loc23_16: type = where_expr %.Self [template = constants.%I.type] {
// CHECK:STDOUT: requirement_rewrite %T.ref, <error>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @I {
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1]
// CHECK:STDOUT: %T: type = assoc_const_decl T [template]
// CHECK:STDOUT: %.loc11: %.1 = assoc_entity element0, %T [template = constants.%.2]
// CHECK:STDOUT:
Expand All @@ -76,10 +112,67 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: witness = (%T)
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: %.loc16_6.2 as %.loc16_16 {
// CHECK:STDOUT: generic interface @ImplicitAs(constants.%Dest: type) {
// CHECK:STDOUT: %Dest: type = bind_symbolic_name Dest, 0 [symbolic = %Dest (constants.%Dest)]
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: %ImplicitAs.type: type = interface_type @ImplicitAs, @ImplicitAs(%Dest) [symbolic = %ImplicitAs.type (constants.%ImplicitAs.type.2)]
// CHECK:STDOUT: %Self: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self (constants.%Self.3)]
// CHECK:STDOUT: %Convert.type: type = fn_type @Convert, @ImplicitAs(%Dest) [symbolic = %Convert.type (constants.%Convert.type.1)]
// CHECK:STDOUT: %Convert: @ImplicitAs.%Convert.type (%Convert.type.1) = struct_value () [symbolic = %Convert (constants.%Convert.1)]
// CHECK:STDOUT: %.1: type = assoc_entity_type @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2), @ImplicitAs.%Convert.type (%Convert.type.1) [symbolic = %.1 (constants.%.4)]
// CHECK:STDOUT: %.2: @ImplicitAs.%.1 (%.4) = assoc_entity element0, imports.%import_ref.6 [symbolic = %.2 (constants.%.5)]
// CHECK:STDOUT:
// CHECK:STDOUT: interface {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = imports.%import_ref.3
// CHECK:STDOUT: .Convert = imports.%import_ref.4
// CHECK:STDOUT: witness = (imports.%import_ref.5)
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: %.loc23_6.2 as %.loc23_16 {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = <error>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Bool() -> type = "bool.make_type";
// CHECK:STDOUT:
// CHECK:STDOUT: generic fn @Convert(constants.%Dest: type, constants.%Self.2: @ImplicitAs.%ImplicitAs.type (%ImplicitAs.type.2)) {
// CHECK:STDOUT: %Dest: type = bind_symbolic_name Dest, 0 [symbolic = %Dest (constants.%Dest)]
// CHECK:STDOUT: %ImplicitAs.type: type = interface_type @ImplicitAs, @ImplicitAs(%Dest) [symbolic = %ImplicitAs.type (constants.%ImplicitAs.type.2)]
// CHECK:STDOUT: %Self: %ImplicitAs.type.2 = bind_symbolic_name Self, 1 [symbolic = %Self (constants.%Self.3)]
// CHECK:STDOUT:
// CHECK:STDOUT: fn[%self: @Convert.%Self (%Self.3)]() -> @Convert.%Dest (%Dest);
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @ImplicitAs(constants.%Dest) {
// CHECK:STDOUT: %Dest => constants.%Dest
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @ImplicitAs(@ImplicitAs.%Dest) {
// CHECK:STDOUT: %Dest => constants.%Dest
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @ImplicitAs(@Convert.%Dest) {
// CHECK:STDOUT: %Dest => constants.%Dest
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @Convert(constants.%Dest, constants.%Self.2) {
// CHECK:STDOUT: %Dest => constants.%Dest
// CHECK:STDOUT: %ImplicitAs.type => constants.%ImplicitAs.type.2
// CHECK:STDOUT: %Self => constants.%Self.2
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @ImplicitAs(constants.%.1) {
// CHECK:STDOUT: %Dest => constants.%.1
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: %ImplicitAs.type => constants.%ImplicitAs.type.3
// CHECK:STDOUT: %Self => constants.%Self.3
// CHECK:STDOUT: %Convert.type => constants.%Convert.type.2
// CHECK:STDOUT: %Convert => constants.%Convert.2
// CHECK:STDOUT: %.1 => constants.%.6
// CHECK:STDOUT: %.2 => constants.%.7
// CHECK:STDOUT: }
// CHECK:STDOUT:
Loading

0 comments on commit 6dbeda6

Please sign in to comment.