diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 85769ef465ac..b390cd4e6bfc 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -170,6 +170,30 @@ auto Context::FollowNameReferences(SemIR::NodeId node_id) -> SemIR::NodeId { return node_id; } +auto Context::GetConstantValue(SemIR::NodeId node_id) -> SemIR::NodeId { + // TODO: The constant value of a node should be computed as we build the + // node, or at least cached once computed. + while (true) { + auto node = nodes().Get(node_id); + switch (node.kind()) { + case SemIR::NameReference::Kind: + node_id = node.As().value_id; + break; + + case SemIR::BindName::Kind: + node_id = node.As().value_id; + break; + + case SemIR::Field::Kind: + return node_id; + + default: + // TODO: Handle the remaining cases. + return SemIR::NodeId::Invalid; + } + } +} + template static auto AddDominatedBlockAndBranchImpl(Context& context, Parse::Node parse_node, Args... args) @@ -454,18 +478,26 @@ class TypeCompleter { // Makes a value representation that uses pass-by-copy, copying the given // type. - auto MakeCopyRepresentation(SemIR::TypeId rep_id) const + auto MakeCopyRepresentation( + SemIR::TypeId rep_id, + SemIR::ValueRepresentation::AggregateKind aggregate_kind = + SemIR::ValueRepresentation::NotAggregate) const -> SemIR::ValueRepresentation { - return {.kind = SemIR::ValueRepresentation::Copy, .type_id = rep_id}; + return {.kind = SemIR::ValueRepresentation::Copy, + .aggregate_kind = aggregate_kind, + .type_id = rep_id}; } // Makes a value representation that uses pass-by-address with the given // pointee type. - auto MakePointerRepresentation(Parse::Node parse_node, - SemIR::TypeId pointee_id) const + auto MakePointerRepresentation( + Parse::Node parse_node, SemIR::TypeId pointee_id, + SemIR::ValueRepresentation::AggregateKind aggregate_kind = + SemIR::ValueRepresentation::NotAggregate) const -> SemIR::ValueRepresentation { // TODO: Should we add `const` qualification to `pointee_id`? return {.kind = SemIR::ValueRepresentation::Pointer, + .aggregate_kind = aggregate_kind, .type_id = context_.GetPointerType(parse_node, pointee_id)}; } @@ -517,10 +549,33 @@ class TypeCompleter { llvm_unreachable("All builtin kinds were handled above"); } + auto BuildStructOrTupleValueRepresentation(Parse::Node parse_node, + std::size_t num_elements, + SemIR::TypeId elementwise_rep, + bool same_as_object_rep) const + -> SemIR::ValueRepresentation { + SemIR::ValueRepresentation::AggregateKind aggregate_kind = + same_as_object_rep ? SemIR::ValueRepresentation::ValueAndObjectAggregate + : SemIR::ValueRepresentation::ValueAggregate; + + if (num_elements == 1) { + // The value representation for a struct or tuple with a single element + // is a struct or tuple containing the value representation of the + // element. + // TODO: Consider doing the same whenever `elementwise_rep` is + // sufficiently small. + return MakeCopyRepresentation(elementwise_rep, aggregate_kind); + } + // For a struct or tuple with multiple fields, we use a pointer + // to the elementwise value representation. + return MakePointerRepresentation(parse_node, elementwise_rep, + aggregate_kind); + } + auto BuildStructTypeValueRepresentation(SemIR::TypeId type_id, SemIR::StructType struct_type) const -> SemIR::ValueRepresentation { - // TODO: Share code with tuples. + // TODO: Share more code with tuples. auto fields = context_.node_blocks().Get(struct_type.fields_id); if (fields.empty()) { return MakeEmptyRepresentation(struct_type.parse_node); @@ -547,21 +602,14 @@ class TypeCompleter { : context_.CanonicalizeStructType( struct_type.parse_node, context_.node_blocks().Add(value_rep_fields)); - if (fields.size() == 1) { - // The value representation for a struct with a single field is a - // struct containing the value representation of the field. - // TODO: Consider doing the same for structs with multiple small - // fields. - return MakeCopyRepresentation(value_rep); - } - // For a struct with multiple fields, we use a pointer representation. - return MakePointerRepresentation(struct_type.parse_node, value_rep); + return BuildStructOrTupleValueRepresentation( + struct_type.parse_node, fields.size(), value_rep, same_as_object_rep); } auto BuildTupleTypeValueRepresentation(SemIR::TypeId type_id, SemIR::TupleType tuple_type) const -> SemIR::ValueRepresentation { - // TODO: Share code with structs. + // TODO: Share more code with structs. auto elements = context_.type_blocks().Get(tuple_type.elements_id); if (elements.empty()) { return MakeEmptyRepresentation(tuple_type.parse_node); @@ -584,15 +632,8 @@ class TypeCompleter { ? type_id : context_.CanonicalizeTupleType(tuple_type.parse_node, value_rep_elements); - if (elements.size() == 1) { - // The value representation for a tuple with a single element is a - // tuple containing the value representation of that element. - // TODO: Consider doing the same for tuples with multiple small - // elements. - return MakeCopyRepresentation(value_rep); - } - // For a tuple with multiple elements, we use a pointer representation. - return MakePointerRepresentation(tuple_type.parse_node, value_rep); + return BuildStructOrTupleValueRepresentation( + tuple_type.parse_node, elements.size(), value_rep, same_as_object_rep); } // Builds and returns the value representation for the given type. All nested @@ -621,6 +662,7 @@ class TypeCompleter { case SemIR::BranchWithArg::Kind: case SemIR::Call::Kind: case SemIR::ClassDeclaration::Kind: + case SemIR::ClassFieldAccess::Kind: case SemIR::Dereference::Kind: case SemIR::Field::Kind: case SemIR::FunctionDeclaration::Kind: @@ -660,7 +702,9 @@ class TypeCompleter { // For arrays, it's convenient to always use a pointer representation, // even when the array has zero or one element, in order to support // indexing. - return MakePointerRepresentation(node.parse_node(), type_id); + return MakePointerRepresentation( + node.parse_node(), type_id, + SemIR::ValueRepresentation::ObjectAggregate); } case SemIR::StructType::Kind: @@ -677,9 +721,11 @@ class TypeCompleter { // TODO: Support customized value representations for classes. // TODO: Pick a better value representation when possible. return MakePointerRepresentation( - node.parse_node(), context_.classes() - .Get(node.As().class_id) - .object_representation_id); + node.parse_node(), + context_.classes() + .Get(node.As().class_id) + .object_representation_id, + SemIR::ValueRepresentation::ObjectAggregate); case SemIR::Builtin::Kind: CARBON_FATAL() << "Builtins should be named as cross-references"; diff --git a/toolchain/check/context.h b/toolchain/check/context.h index f80f5f198064..eb837751b195 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -96,6 +96,9 @@ class Context { // Follows NameReference nodes to find the value named by a given node. auto FollowNameReferences(SemIR::NodeId node_id) -> SemIR::NodeId; + // Gets the constant value of the given node, if it has one. + auto GetConstantValue(SemIR::NodeId node_id) -> SemIR::NodeId; + // Adds a `Branch` node branching to a new node block, and returns the ID of // the new block. All paths to the branch target must go through the current // block, though not necessarily through this branch. diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 5b6719e40d42..982d745d7675 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -55,14 +55,72 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) return true; } + // If the base isn't a scope, it must have a complete type. + auto base_type_id = context.nodes().Get(base_id).type_id(); + if (!context.TryToCompleteType(base_type_id, [&] { + CARBON_DIAGNOSTIC(IncompleteTypeInMemberAccess, Error, + "Member access into object of incomplete type `{0}`.", + std::string); + return context.emitter().Build( + context.nodes().Get(base_id).parse_node(), + IncompleteTypeInMemberAccess, + context.sem_ir().StringifyType(base_type_id, true)); + })) { + context.node_stack().Push(parse_node, SemIR::NodeId::BuiltinError); + return true; + } + // Materialize a temporary for the base expression if necessary. base_id = ConvertToValueOrReferenceExpression(context, base_id); - auto base_type_id = context.nodes().Get(base_id).type_id(); + base_type_id = context.nodes().Get(base_id).type_id(); auto base_type = context.nodes().Get( context.sem_ir().GetTypeAllowBuiltinTypes(base_type_id)); switch (base_type.kind()) { + case SemIR::ClassType::Kind: { + // Perform lookup for the name in the class scope. + auto class_scope_id = context.classes() + .Get(base_type.As().class_id) + .scope_id; + auto member_id = context.LookupName(parse_node, name_id, class_scope_id, + /*print_diagnostics=*/true); + if (!member_id.is_valid()) { + break; + } + + // Perform instance binding if we found an instance member. + auto member_type_id = context.nodes().Get(member_id).type_id(); + auto member_type_node = context.nodes().Get( + context.sem_ir().GetTypeAllowBuiltinTypes(member_type_id)); + if (auto unbound_field_type = + member_type_node.TryAs()) { + // TODO: Check that the unbound field type describes a member of this + // class. Perform a conversion of the base if necessary. + + // Find the named field and build a field access expression. + auto field_id = context.GetConstantValue(member_id); + CARBON_CHECK(field_id.is_valid()) + << "Non-constant value " << context.nodes().Get(member_id) + << " of unbound field type"; + auto field = context.nodes().Get(field_id).TryAs(); + CARBON_CHECK(field) + << "Unexpected value " << context.nodes().Get(field_id) + << " for field name expression"; + context.AddNodeAndPush( + parse_node, SemIR::ClassFieldAccess{ + parse_node, unbound_field_type->field_type_id, + base_id, field->index}); + return true; + } + + // For a non-instance member, the result is that member. + // TODO: Track that this was named within `base_id`. + context.AddNodeAndPush( + parse_node, + SemIR::NameReference{parse_node, member_type_id, name_id, member_id}); + return true; + } case SemIR::StructType::Kind: { auto refs = context.node_blocks().Get( base_type.As().fields_id); @@ -84,6 +142,9 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) context.strings().Get(name_id)); break; } + // TODO: `ConstType` should support member access just like the + // corresponding non-const type, except that the result should have `const` + // type if it creates a reference expression performing field access. default: { if (base_type_id != SemIR::TypeId::Error) { CARBON_DIAGNOSTIC(QualifiedExpressionUnsupported, Error, diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index 2464d2b78a44..dbcf2049b8cc 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/toolchain/check/handle_pattern_binding.cpp @@ -55,8 +55,11 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { parse_node, context.GetBuiltinType(SemIR::BuiltinKind::TypeType), class_info.self_type_id, cast_type_id}); value_type_id = context.CanonicalizeType(field_type_node_id); - value_id = - context.AddNode(SemIR::Field{parse_node, value_type_id, name_id}); + value_id = context.AddNode( + SemIR::Field{parse_node, value_type_id, name_id, + SemIR::MemberIndex(context.args_type_info_stack() + .PeekCurrentBlockContents() + .size())}); // Add a corresponding field to the object representation of the class. context.args_type_info_stack().AddNode( diff --git a/toolchain/check/testdata/class/basic.carbon b/toolchain/check/testdata/class/basic.carbon index 104e0a2f9585..09a6a487d4b9 100644 --- a/toolchain/check/testdata/class/basic.carbon +++ b/toolchain/check/testdata/class/basic.carbon @@ -34,7 +34,7 @@ fn Run() -> i32 { // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: %.loc14_8.1: type = unbound_field_type Class, i32 -// CHECK:STDOUT: %.loc14_8.2: = field "k" +// CHECK:STDOUT: %.loc14_8.2: = field "k", member0 // CHECK:STDOUT: %k: = bind_name "k", %.loc14_8.2 // CHECK:STDOUT: // CHECK:STDOUT: !members: diff --git a/toolchain/check/testdata/class/fail_incomplete.carbon b/toolchain/check/testdata/class/fail_incomplete.carbon index c89e8a0afce9..6a45264922c6 100644 --- a/toolchain/check/testdata/class/fail_incomplete.carbon +++ b/toolchain/check/testdata/class/fail_incomplete.carbon @@ -49,7 +49,7 @@ fn ConvertFromStruct() -> Class { return {}; } // TODO: } fn MemberAccess(p: Class*) -> i32 { - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:11: ERROR: Invalid use of incomplete type `Class`. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:11: ERROR: Member access into object of incomplete type `Class`. // CHECK:STDERR: return (*p).n; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-48]]:1: Class was forward declared here. diff --git a/toolchain/check/testdata/class/fail_unbound_field.carbon b/toolchain/check/testdata/class/fail_unbound_field.carbon index 0ae30c13c1e4..47a6d0e27035 100644 --- a/toolchain/check/testdata/class/fail_unbound_field.carbon +++ b/toolchain/check/testdata/class/fail_unbound_field.carbon @@ -31,7 +31,7 @@ fn G() -> i32 { // CHECK:STDOUT: // CHECK:STDOUT: class @Class { // CHECK:STDOUT: %.loc8_12.1: type = unbound_field_type Class, i32 -// CHECK:STDOUT: %.loc8_12.2: = field "field" +// CHECK:STDOUT: %.loc8_12.2: = field "field", member0 // CHECK:STDOUT: %field: = bind_name "field", %.loc8_12.2 // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_unknown_member.carbon b/toolchain/check/testdata/class/fail_unknown_member.carbon new file mode 100644 index 000000000000..57aa92cea987 --- /dev/null +++ b/toolchain/check/testdata/class/fail_unknown_member.carbon @@ -0,0 +1,41 @@ +// 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 +// +// AUTOUPDATE + +class Class { + var n: i32; +} + +fn G(c: Class) -> i32 { + // TODO: Mention the scope in which we looked for the name. + // CHECK:STDERR: fail_unknown_member.carbon:[[@LINE+3]]:11: ERROR: Name `something` not found. + // CHECK:STDERR: return c.something; + // CHECK:STDERR: ^ + return c.something; +} + +// CHECK:STDOUT: file "fail_unknown_member.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc9: type = struct_type {.n: i32} +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "n", member0 +// CHECK:STDOUT: %n: = bind_name "n", %.loc8_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .n = %n +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%c: Class) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc7: type = ptr_type {.n: i32} +// CHECK:STDOUT: %c.ref: Class = name_reference "c", %c +// CHECK:STDOUT: %something.ref: = name_reference "something", +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/field_access.carbon b/toolchain/check/testdata/class/field_access.carbon new file mode 100644 index 000000000000..df8fd6746e9e --- /dev/null +++ b/toolchain/check/testdata/class/field_access.carbon @@ -0,0 +1,61 @@ +// 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 +// +// AUTOUPDATE + +class Class { + var j: i32; + var k: i32; +} + +fn Run() -> i32 { + var c: Class; + c.j = 1; + c.k = 2; + return c.j + c.k; +} + +// CHECK:STDOUT: file "field_access.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {.j: i32, .k: i32} +// CHECK:STDOUT: %Run: = fn_decl @Run +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "j", member0 +// CHECK:STDOUT: %j: = bind_name "j", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "k", member1 +// CHECK:STDOUT: %k: = bind_name "k", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .j = %j +// CHECK:STDOUT: .k = %k +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Run() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc10: type = ptr_type {.j: i32, .k: i32} +// CHECK:STDOUT: %c.var: ref Class = var "c" +// CHECK:STDOUT: %c: ref Class = bind_name "c", %c.var +// CHECK:STDOUT: %c.ref.loc14: ref Class = name_reference "c", %c +// CHECK:STDOUT: %.loc14_4: ref i32 = class_field_access %c.ref.loc14, member0 +// CHECK:STDOUT: %.loc14_9: i32 = int_literal 1 +// CHECK:STDOUT: assign %.loc14_4, %.loc14_9 +// CHECK:STDOUT: %c.ref.loc15: ref Class = name_reference "c", %c +// CHECK:STDOUT: %.loc15_4: ref i32 = class_field_access %c.ref.loc15, member1 +// CHECK:STDOUT: %.loc15_9: i32 = int_literal 2 +// CHECK:STDOUT: assign %.loc15_4, %.loc15_9 +// CHECK:STDOUT: %c.ref.loc16_10: ref Class = name_reference "c", %c +// CHECK:STDOUT: %.loc16_11.1: ref i32 = class_field_access %c.ref.loc16_10, member0 +// CHECK:STDOUT: %c.ref.loc16_16: ref Class = name_reference "c", %c +// CHECK:STDOUT: %.loc16_17.1: ref i32 = class_field_access %c.ref.loc16_16, member1 +// CHECK:STDOUT: %.loc16_11.2: i32 = bind_value %.loc16_11.1 +// CHECK:STDOUT: %.loc16_17.2: i32 = bind_value %.loc16_17.1 +// CHECK:STDOUT: %.loc16_14: i32 = add %.loc16_11.2, %.loc16_17.2 +// CHECK:STDOUT: return %.loc16_14 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/field_access_in_value.carbon b/toolchain/check/testdata/class/field_access_in_value.carbon new file mode 100644 index 000000000000..833920c28337 --- /dev/null +++ b/toolchain/check/testdata/class/field_access_in_value.carbon @@ -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 +// +// AUTOUPDATE + +class Class { + var j: i32; + var k: i32; +} + +fn Run() -> i32 { + var cv: Class; + cv.j = 1; + cv.k = 2; + let c: Class = cv; + return c.j + c.k; +} + +// CHECK:STDOUT: file "field_access_in_value.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {.j: i32, .k: i32} +// CHECK:STDOUT: %Run: = fn_decl @Run +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "j", member0 +// CHECK:STDOUT: %j: = bind_name "j", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "k", member1 +// CHECK:STDOUT: %k: = bind_name "k", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .j = %j +// CHECK:STDOUT: .k = %k +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Run() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref.loc13: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc10: type = ptr_type {.j: i32, .k: i32} +// CHECK:STDOUT: %cv.var: ref Class = var "cv" +// CHECK:STDOUT: %cv: ref Class = bind_name "cv", %cv.var +// CHECK:STDOUT: %cv.ref.loc14: ref Class = name_reference "cv", %cv +// CHECK:STDOUT: %.loc14_5: ref i32 = class_field_access %cv.ref.loc14, member0 +// CHECK:STDOUT: %.loc14_10: i32 = int_literal 1 +// CHECK:STDOUT: assign %.loc14_5, %.loc14_10 +// CHECK:STDOUT: %cv.ref.loc15: ref Class = name_reference "cv", %cv +// CHECK:STDOUT: %.loc15_5: ref i32 = class_field_access %cv.ref.loc15, member1 +// CHECK:STDOUT: %.loc15_10: i32 = int_literal 2 +// CHECK:STDOUT: assign %.loc15_5, %.loc15_10 +// CHECK:STDOUT: %Class.ref.loc16: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %cv.ref.loc16: ref Class = name_reference "cv", %cv +// CHECK:STDOUT: %.loc16: Class = bind_value %cv.ref.loc16 +// CHECK:STDOUT: %c: Class = bind_name "c", %.loc16 +// CHECK:STDOUT: %c.ref.loc17_10: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc17_11.1: ref i32 = class_field_access %c.ref.loc17_10, member0 +// CHECK:STDOUT: %c.ref.loc17_16: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc17_17.1: ref i32 = class_field_access %c.ref.loc17_16, member1 +// CHECK:STDOUT: %.loc17_11.2: i32 = bind_value %.loc17_11.1 +// CHECK:STDOUT: %.loc17_17.2: i32 = bind_value %.loc17_17.1 +// CHECK:STDOUT: %.loc17_14: i32 = add %.loc17_11.2, %.loc17_17.2 +// CHECK:STDOUT: return %.loc17_14 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/static_method.carbon b/toolchain/check/testdata/class/static_method.carbon new file mode 100644 index 000000000000..b9fe68b12bae --- /dev/null +++ b/toolchain/check/testdata/class/static_method.carbon @@ -0,0 +1,46 @@ +// 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 +// +// AUTOUPDATE + +class Class { + fn F() -> i32; +} + +fn Run() -> i32 { + var c: Class; + return c.F(); +} + +// CHECK:STDOUT: file "static_method.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc9: type = struct_type {} +// CHECK:STDOUT: %Run: = fn_decl @Run +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Run() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc9: type = tuple_type () +// CHECK:STDOUT: %.loc7: type = ptr_type {} +// CHECK:STDOUT: %c.var: ref Class = var "c" +// CHECK:STDOUT: %c: ref Class = bind_name "c", %c.var +// CHECK:STDOUT: %c.ref: ref Class = name_reference "c", %c +// CHECK:STDOUT: %F.ref: = name_reference "F", @Class.%F +// CHECK:STDOUT: %.loc13_13.1: init i32 = call %F.ref() +// CHECK:STDOUT: %.loc13_13.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc13_13.3: ref i32 = temporary %.loc13_13.2, %.loc13_13.1 +// CHECK:STDOUT: %.loc13_13.4: i32 = bind_value %.loc13_13.3 +// CHECK:STDOUT: return %.loc13_13.4 +// CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index b3225af80ea4..1e76d7c7ed5b 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -137,6 +137,7 @@ CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionParam) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionReturnType) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInInitialization) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInLetDeclaration) +CARBON_DIAGNOSTIC_KIND(IncompleteTypeInMemberAccess) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInValueConversion) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInVarDeclaration) CARBON_DIAGNOSTIC_KIND(InvalidArrayExpression) diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index bfc85abda369..60c00f6c8d55 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -166,6 +166,113 @@ auto HandleClassDeclaration(FunctionContext& /*context*/, // No action to perform. } +// Extracts an element of an aggregate, such as a struct, tuple, or class, by +// index. Depending on the expression category and value representation of the +// aggregate input, this will either produce a value or a reference. +static auto GetAggregateElement(FunctionContext& context, + SemIR::NodeId aggr_node_id, + SemIR::MemberIndex idx, + SemIR::TypeId result_type_id, llvm::Twine name) + -> llvm::Value* { + auto aggr_node = context.sem_ir().nodes().Get(aggr_node_id); + auto* aggr_value = context.GetLocal(aggr_node_id); + + switch (SemIR::GetExpressionCategory(context.sem_ir(), aggr_node_id)) { + case SemIR::ExpressionCategory::Error: + case SemIR::ExpressionCategory::NotExpression: + case SemIR::ExpressionCategory::Initializing: + case SemIR::ExpressionCategory::Mixed: + CARBON_FATAL() << "Unexpected expression category for aggregate access"; + + case SemIR::ExpressionCategory::Value: { + auto value_rep = + SemIR::GetValueRepresentation(context.sem_ir(), aggr_node.type_id()); + CARBON_CHECK(value_rep.aggregate_kind != + SemIR::ValueRepresentation::NotAggregate) + << "aggregate type should have aggregate value representation"; + switch (value_rep.kind) { + case SemIR::ValueRepresentation::Unknown: + CARBON_FATAL() << "Lowering access to incomplete aggregate type"; + case SemIR::ValueRepresentation::None: + return aggr_value; + case SemIR::ValueRepresentation::Copy: + // We are holding the values of the aggregate directly, elementwise. + return context.builder().CreateExtractValue(aggr_value, idx.index, + name); + case SemIR::ValueRepresentation::Pointer: { + // The value representation is a pointer to an aggregate that we want + // to index into. + auto pointee_type_id = + context.sem_ir().GetPointeeType(value_rep.type_id); + auto* value_type = context.GetType(pointee_type_id); + auto* elem_ptr = context.builder().CreateStructGEP( + value_type, aggr_value, idx.index, name); + + if (!value_rep.elements_are_values()) { + // `elem_ptr` points to an object representation, which is our + // result. + return elem_ptr; + } + + // `elem_ptr` points to a value representation. Load it. + auto result_value_type_id = + SemIR::GetValueRepresentation(context.sem_ir(), result_type_id) + .type_id; + return context.builder().CreateLoad( + context.GetType(result_value_type_id), elem_ptr, name + ".load"); + } + case SemIR::ValueRepresentation::Custom: + CARBON_FATAL() + << "Aggregate should never have custom value representation"; + } + } + + case SemIR::ExpressionCategory::DurableReference: + case SemIR::ExpressionCategory::EphemeralReference: { + // Just locate the aggregate element. + auto* aggr_type = context.GetType(aggr_node.type_id()); + return context.builder().CreateStructGEP(aggr_type, aggr_value, idx.index, + name); + } + } +} + +static auto GetStructFieldName(FunctionContext& context, + SemIR::TypeId struct_type_id, + SemIR::MemberIndex index) -> llvm::StringRef { + auto fields = context.sem_ir().node_blocks().Get( + context.sem_ir() + .nodes() + .GetAs( + context.sem_ir().types().Get(struct_type_id).node_id) + .fields_id); + auto field = context.sem_ir().nodes().GetAs( + fields[index.index]); + return context.sem_ir().strings().Get(field.name_id); +} + +auto HandleClassFieldAccess(FunctionContext& context, SemIR::NodeId node_id, + SemIR::ClassFieldAccess node) -> void { + // Find the class that we're performing access into. + auto class_type_id = context.sem_ir().nodes().Get(node.base_id).type_id(); + auto class_id = + context.sem_ir() + .nodes() + .GetAs( + context.sem_ir().GetTypeAllowBuiltinTypes(class_type_id)) + .class_id; + auto& class_info = context.sem_ir().classes().Get(class_id); + + // Translate the class field access into a struct access on the object + // representation. + context.SetLocal( + node_id, + GetAggregateElement( + context, node.base_id, node.index, node.type_id, + GetStructFieldName(context, class_info.object_representation_id, + node.index))); +} + auto HandleDereference(FunctionContext& context, SemIR::NodeId node_id, SemIR::Dereference node) -> void { context.SetLocal(node_id, context.GetLocal(node.pointer_id)); @@ -283,82 +390,13 @@ auto HandleStringLiteral(FunctionContext& /*context*/, CARBON_FATAL() << "TODO: Add support: " << node; } -// Extracts an element of either a struct or a tuple by index. Depending on the -// expression category of the aggregate input, this will either produce a value -// or a reference. -static auto GetStructOrTupleElement(FunctionContext& context, - SemIR::NodeId aggr_node_id, unsigned idx, - SemIR::TypeId result_type_id, - llvm::Twine name) -> llvm::Value* { - auto aggr_node = context.sem_ir().nodes().Get(aggr_node_id); - auto* aggr_value = context.GetLocal(aggr_node_id); - - switch (SemIR::GetExpressionCategory(context.sem_ir(), aggr_node_id)) { - case SemIR::ExpressionCategory::Error: - case SemIR::ExpressionCategory::NotExpression: - case SemIR::ExpressionCategory::Initializing: - case SemIR::ExpressionCategory::Mixed: - CARBON_FATAL() << "Unexpected expression category for aggregate access"; - - case SemIR::ExpressionCategory::Value: { - auto value_rep = - SemIR::GetValueRepresentation(context.sem_ir(), aggr_node.type_id()); - switch (value_rep.kind) { - case SemIR::ValueRepresentation::Unknown: - CARBON_FATAL() << "Lowering access to incomplete aggregate type"; - case SemIR::ValueRepresentation::None: - return aggr_value; - case SemIR::ValueRepresentation::Copy: - // We are holding the values of the aggregate directly, elementwise. - return context.builder().CreateExtractValue(aggr_value, idx, name); - case SemIR::ValueRepresentation::Pointer: { - // The value representation is a pointer to an aggregate that we want - // to index into. - auto pointee_type_id = - context.sem_ir().GetPointeeType(value_rep.type_id); - auto* value_type = context.GetType(pointee_type_id); - auto* elem_ptr = context.builder().CreateStructGEP( - value_type, aggr_value, idx, name); - auto result_value_type_id = - SemIR::GetValueRepresentation(context.sem_ir(), result_type_id) - .type_id; - return context.builder().CreateLoad( - context.GetType(result_value_type_id), elem_ptr, name + ".load"); - } - case SemIR::ValueRepresentation::Custom: - CARBON_FATAL() - << "Aggregate should never have custom value representation"; - } - } - - case SemIR::ExpressionCategory::DurableReference: - case SemIR::ExpressionCategory::EphemeralReference: { - // Just locate the aggregate element. - auto* aggr_type = context.GetType(aggr_node.type_id()); - return context.builder().CreateStructGEP(aggr_type, aggr_value, idx, - name); - } - } -} - auto HandleStructAccess(FunctionContext& context, SemIR::NodeId node_id, SemIR::StructAccess node) -> void { auto struct_type_id = context.sem_ir().nodes().Get(node.struct_id).type_id(); - - // Get type information for member names. - auto fields = context.sem_ir().node_blocks().Get( - context.sem_ir() - .nodes() - .GetAs( - context.sem_ir().types().Get(struct_type_id).node_id) - .fields_id); - auto field = context.sem_ir().nodes().GetAs( - fields[node.index.index]); - auto member_name = context.sem_ir().strings().Get(field.name_id); - - context.SetLocal(node_id, GetStructOrTupleElement(context, node.struct_id, - node.index.index, - node.type_id, member_name)); + context.SetLocal( + node_id, GetAggregateElement( + context, node.struct_id, node.index, node.type_id, + GetStructFieldName(context, struct_type_id, node.index))); } auto HandleStructLiteral(FunctionContext& /*context*/, @@ -454,8 +492,8 @@ auto HandleStructTypeField(FunctionContext& /*context*/, auto HandleTupleAccess(FunctionContext& context, SemIR::NodeId node_id, SemIR::TupleAccess node) -> void { - context.SetLocal( - node_id, GetStructOrTupleElement(context, node.tuple_id, node.index.index, + context.SetLocal(node_id, + GetAggregateElement(context, node.tuple_id, node.index, node.type_id, "tuple.elem")); } @@ -465,9 +503,9 @@ auto HandleTupleIndex(FunctionContext& context, SemIR::NodeId node_id, context.sem_ir().nodes().GetAs(node.index_id); auto index = context.sem_ir().integers().Get(index_node.integer_id).getZExtValue(); - context.SetLocal(node_id, - GetStructOrTupleElement(context, node.tuple_id, index, - node.type_id, "tuple.index")); + context.SetLocal(node_id, GetAggregateElement(context, node.tuple_id, + SemIR::MemberIndex(index), + node.type_id, "tuple.index")); } auto HandleTupleLiteral(FunctionContext& /*context*/, SemIR::NodeId /*node_id*/, diff --git a/toolchain/lower/testdata/class/field.carbon b/toolchain/lower/testdata/class/field.carbon new file mode 100644 index 000000000000..61d233e337ec --- /dev/null +++ b/toolchain/lower/testdata/class/field.carbon @@ -0,0 +1,45 @@ +// 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 +// +// AUTOUPDATE + +class C { + var a: i32; + var b: C*; +} + +fn F(c: C) -> i32 { + return (*c.b).a; +} + +fn Run() -> i32 { + var c: C; + c.a = 1; + c.b = &c; + return F(c); +} + +// CHECK:STDOUT: ; ModuleID = 'field.carbon' +// CHECK:STDOUT: source_filename = "field.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @F(ptr %c) { +// CHECK:STDOUT: %b = getelementptr inbounds { i32, ptr }, ptr %c, i32 0, i32 1 +// CHECK:STDOUT: %1 = load ptr, ptr %b, align 8 +// CHECK:STDOUT: %a = getelementptr inbounds { i32, ptr }, ptr %1, i32 0, i32 0 +// CHECK:STDOUT: %2 = load i32, ptr %a, align 4 +// CHECK:STDOUT: ret i32 %2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @main() { +// CHECK:STDOUT: %c = alloca { i32, ptr }, align 8 +// CHECK:STDOUT: %a = getelementptr inbounds { i32, ptr }, ptr %c, i32 0, i32 0 +// CHECK:STDOUT: store i32 1, ptr %a, align 4 +// CHECK:STDOUT: %b = getelementptr inbounds { i32, ptr }, ptr %c, i32 0, i32 1 +// CHECK:STDOUT: store ptr %c, ptr %b, align 8 +// CHECK:STDOUT: %F = call i32 @F(ptr %c) +// CHECK:STDOUT: %temp = alloca i32, align 4 +// CHECK:STDOUT: store i32 %F, ptr %temp, align 4 +// CHECK:STDOUT: %1 = load i32, ptr %temp, align 4 +// CHECK:STDOUT: ret i32 %1 +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/class/value_access.carbon b/toolchain/lower/testdata/class/value_access.carbon new file mode 100644 index 000000000000..afc7f9452e0a --- /dev/null +++ b/toolchain/lower/testdata/class/value_access.carbon @@ -0,0 +1,23 @@ +// 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 +// +// AUTOUPDATE + +class C { + var a: (i32, i32, i32); +} + +fn F(c: C) -> i32 { + return c.a[1]; +} + +// CHECK:STDOUT: ; ModuleID = 'value_access.carbon' +// CHECK:STDOUT: source_filename = "value_access.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @F(ptr %c) { +// CHECK:STDOUT: %a = getelementptr inbounds { { i32, i32, i32 } }, ptr %c, i32 0, i32 0 +// CHECK:STDOUT: %tuple.index = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1 +// CHECK:STDOUT: %1 = load i32, ptr %tuple.index, align 4 +// CHECK:STDOUT: ret i32 %1 +// CHECK:STDOUT: } diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 6102d67aa704..594bde0d4ce4 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -192,6 +192,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case BranchWithArg::Kind: case Call::Kind: case ClassDeclaration::Kind: + case ClassFieldAccess::Kind: case Dereference::Kind: case Field::Kind: case FunctionDeclaration::Kind: @@ -389,6 +390,7 @@ auto File::StringifyTypeExpression(NodeId outer_node_id, case Builtin::Kind: case Call::Kind: case ClassDeclaration::Kind: + case ClassFieldAccess::Kind: case CrossReference::Kind: case Dereference::Kind: case Field::Kind: @@ -442,6 +444,10 @@ auto File::StringifyTypeExpression(NodeId outer_node_id, auto GetExpressionCategory(const File& file, NodeId node_id) -> ExpressionCategory { const File* ir = &file; + + // The overall expression category if the current node is a value expression. + ExpressionCategory value_category = ExpressionCategory::Value; + while (true) { auto node = ir->nodes().Get(node_id); // clang warns on unhandled enum values; clang-tidy is incorrect here. @@ -492,13 +498,13 @@ auto GetExpressionCategory(const File& file, NodeId node_id) case TupleType::Kind: case UnaryOperatorNot::Kind: case UnboundFieldType::Kind: - return ExpressionCategory::Value; + return value_category; case Builtin::Kind: { if (node.As().builtin_kind == BuiltinKind::Error) { return ExpressionCategory::Error; } - return ExpressionCategory::Value; + return value_category; } case BindName::Kind: { @@ -511,6 +517,15 @@ auto GetExpressionCategory(const File& file, NodeId node_id) continue; } + case ClassFieldAccess::Kind: { + node_id = node.As().base_id; + // A value of class type is a pointer to an object representation. + // Therefore, if the base is a value, the result is an ephemeral + // reference. + value_category = ExpressionCategory::EphemeralReference; + continue; + } + case StructAccess::Kind: { node_id = node.As().struct_id; continue; diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index 78eea2b104d7..e7c1f017a382 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -107,7 +107,7 @@ struct ValueRepresentation : public Printable { // The value representation is a copy of the value. On call boundaries, the // value itself will be passed. `type` is the value type. Copy, - // The value representation is a pointer to an object. When used as a + // The value representation is a pointer to the value. When used as a // parameter, the argument is a reference expression. `type` is the pointee // type. Pointer, @@ -116,8 +116,31 @@ struct ValueRepresentation : public Printable { // TODO: This is not implemented or used yet. Custom, }; + + enum AggregateKind : int8_t { + // This type is not an aggregation of other types. + NotAggregate, + // This type is an aggregate that holds the value representations of its + // elements. + ValueAggregate, + // This type is an aggregate that holds the object representations of its + // elements. + ObjectAggregate, + // This type is an aggregate for which the value and object representation + // of all elements are the same, so it effectively holds both. + ValueAndObjectAggregate, + }; + + // Returns whether this is an aggregate that holds its elements by value. + auto elements_are_values() const { + return aggregate_kind == ValueAggregate || + aggregate_kind == ValueAndObjectAggregate; + } + // The kind of value representation used by this type. Kind kind = Unknown; + // The kind of aggregate representation used by this type. + AggregateKind aggregate_kind = AggregateKind::NotAggregate; // The type used to model the value representation. TypeId type_id = TypeId::Invalid; }; diff --git a/toolchain/sem_ir/node_kind.def b/toolchain/sem_ir/node_kind.def index 3a8a0f44e7ed..b79414c16548 100644 --- a/toolchain/sem_ir/node_kind.def +++ b/toolchain/sem_ir/node_kind.def @@ -33,6 +33,7 @@ CARBON_SEM_IR_NODE_KIND(BranchWithArg) CARBON_SEM_IR_NODE_KIND(Builtin) CARBON_SEM_IR_NODE_KIND(Call) CARBON_SEM_IR_NODE_KIND(ClassDeclaration) +CARBON_SEM_IR_NODE_KIND(ClassFieldAccess) CARBON_SEM_IR_NODE_KIND(ClassType) CARBON_SEM_IR_NODE_KIND(ConstType) CARBON_SEM_IR_NODE_KIND(CrossReference) diff --git a/toolchain/sem_ir/typed_nodes.h b/toolchain/sem_ir/typed_nodes.h index 20de2ece0a09..bae697f9bdb7 100644 --- a/toolchain/sem_ir/typed_nodes.h +++ b/toolchain/sem_ir/typed_nodes.h @@ -190,6 +190,16 @@ struct ClassDeclaration { NodeBlockId decl_block_id; }; +struct ClassFieldAccess { + static constexpr auto Kind = + NodeKind::ClassFieldAccess.Define("class_field_access"); + + Parse::Node parse_node; + TypeId type_id; + NodeId base_id; + MemberIndex index; +}; + struct ClassType { static constexpr auto Kind = NodeKind::ClassType.Define("class_type"); @@ -235,6 +245,7 @@ struct Field { Parse::Node parse_node; TypeId type_id; StringId name_id; + MemberIndex index; }; struct FunctionDeclaration {