Skip to content

Commit 3884d3c

Browse files
authored
Parse and check support for compound member access. (#3790)
On the parsing side, we treat `a.(b)` as a member access whose second operand is a `ParenExpr` rather than a `MemberName`. A new node category is added for the union of `MemberName` and `ParenExpr` to support this. Checking is mostly reusing the same pieces we already have for simple member access. Compound member access is in most ways a simplified form of simple member access because it doesn't need to do any lookup.
1 parent 3e722bb commit 3884d3c

21 files changed

+924
-20
lines changed

toolchain/check/handle_name.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
#include "toolchain/check/context.h"
66
#include "toolchain/check/member_access.h"
77
#include "toolchain/lex/token_kind.h"
8+
#include "toolchain/parse/typed_nodes.h"
89
#include "toolchain/sem_ir/inst.h"
910
#include "toolchain/sem_ir/typed_insts.h"
1011

1112
namespace Carbon::Check {
1213

1314
auto HandleMemberAccessExpr(Context& context, Parse::MemberAccessExprId node_id)
1415
-> bool {
15-
SemIR::NameId name_id = context.node_stack().PopName();
16-
auto base_id = context.node_stack().PopExpr();
17-
auto member_id = PerformMemberAccess(context, node_id, base_id, name_id);
18-
context.node_stack().Push(node_id, member_id);
16+
if (context.node_stack().PeekIs<Parse::NodeKind::ParenExpr>()) {
17+
auto member_expr_id = context.node_stack().PopExpr();
18+
auto base_id = context.node_stack().PopExpr();
19+
auto member_id =
20+
PerformCompoundMemberAccess(context, node_id, base_id, member_expr_id);
21+
context.node_stack().Push(node_id, member_id);
22+
} else {
23+
SemIR::NameId name_id = context.node_stack().PopName();
24+
auto base_id = context.node_stack().PopExpr();
25+
auto member_id = PerformMemberAccess(context, node_id, base_id, name_id);
26+
context.node_stack().Push(node_id, member_id);
27+
}
1928
return true;
2029
}
2130

toolchain/check/member_access.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ static auto PerformInstanceBinding(Context& context,
269269
context.GetBuiltinType(SemIR::BuiltinKind::FunctionType)) {
270270
// Find the named function and check whether it's an instance method.
271271
auto function_name_id = context.constant_values().Get(member_id);
272+
if (function_name_id == SemIR::ConstantId::Error) {
273+
return SemIR::InstId::BuiltinError;
274+
}
275+
272276
CARBON_CHECK(function_name_id.is_constant())
273277
<< "Non-constant value " << context.insts().Get(member_id)
274278
<< " of function type";
@@ -366,4 +370,42 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
366370
return member_id;
367371
}
368372

373+
auto PerformCompoundMemberAccess(Context& context,
374+
Parse::MemberAccessExprId node_id,
375+
SemIR::InstId base_id,
376+
SemIR::InstId member_expr_id)
377+
-> SemIR::InstId {
378+
// Materialize a temporary for the base expression if necessary.
379+
base_id = ConvertToValueOrRefExpr(context, base_id);
380+
auto base_type_id = context.insts().Get(base_id).type_id();
381+
auto base_type_const_id = context.types().GetConstantId(base_type_id);
382+
383+
auto member_id = member_expr_id;
384+
auto member = context.insts().Get(member_id);
385+
386+
// If the member expression names an associated entity, impl lookup is always
387+
// performed using the type of the base expression.
388+
if (auto assoc_type = context.types().TryGetAs<SemIR::AssociatedEntityType>(
389+
member.type_id())) {
390+
member_id =
391+
PerformImplLookup(context, base_type_const_id, *assoc_type, member_id);
392+
}
393+
394+
// Perform instance binding if we found an instance member.
395+
member_id = PerformInstanceBinding(context, node_id, base_id, member_id);
396+
397+
// If we didn't perform impl lookup or instance binding, that's an error
398+
// because the base expression is not used for anything.
399+
if (member_id == member_expr_id) {
400+
CARBON_DIAGNOSTIC(CompoundMemberAccessDoesNotUseBase, Error,
401+
"Member name of type `{0}` in compound member access is "
402+
"not an instance member or an interface member.",
403+
SemIR::TypeId);
404+
context.emitter().Emit(node_id, CompoundMemberAccessDoesNotUseBase,
405+
member.type_id());
406+
}
407+
408+
return member_id;
409+
}
410+
369411
} // namespace Carbon::Check

toolchain/check/member_access.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id,
1616
SemIR::InstId base_id, SemIR::NameId name_id)
1717
-> SemIR::InstId;
1818

19+
// Creates SemIR to perform a compound member access with base expression
20+
// `base_id` and member name expression `member_expr_id`. Returns the result of
21+
// the access.
22+
auto PerformCompoundMemberAccess(Context& context,
23+
Parse::MemberAccessExprId node_id,
24+
SemIR::InstId base_id,
25+
SemIR::InstId member_expr_id) -> SemIR::InstId;
26+
1927
} // namespace Carbon::Check
2028

2129
#endif // CARBON_TOOLCHAIN_CHECK_MEMBER_ACCESS_H_
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
// AUTOUPDATE
6+
7+
base class Base {
8+
var a: i32;
9+
var b: i32;
10+
var c: i32;
11+
}
12+
13+
class Derived {
14+
extend base: Base;
15+
16+
var d: i32;
17+
var e: i32;
18+
}
19+
20+
fn AccessDerived(d: Derived) -> i32 {
21+
return d.(Derived.d);
22+
}
23+
24+
fn AccessBase(d: Derived) -> i32 {
25+
return d.(Base.b);
26+
}
27+
28+
// CHECK:STDOUT: --- compound_field.carbon
29+
// CHECK:STDOUT:
30+
// CHECK:STDOUT: constants {
31+
// CHECK:STDOUT: %Base: type = class_type @Base [template]
32+
// CHECK:STDOUT: %.1: type = unbound_element_type Base, i32 [template]
33+
// CHECK:STDOUT: %.2: type = struct_type {.a: i32, .b: i32, .c: i32} [template]
34+
// CHECK:STDOUT: %Derived: type = class_type @Derived [template]
35+
// CHECK:STDOUT: %.3: type = ptr_type {.a: i32, .b: i32, .c: i32} [template]
36+
// CHECK:STDOUT: %.4: type = unbound_element_type Derived, Base [template]
37+
// CHECK:STDOUT: %.5: type = unbound_element_type Derived, i32 [template]
38+
// CHECK:STDOUT: %.6: type = struct_type {.base: Base, .d: i32, .e: i32} [template]
39+
// CHECK:STDOUT: %.7: type = struct_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} [template]
40+
// CHECK:STDOUT: %.8: type = ptr_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} [template]
41+
// CHECK:STDOUT: %.9: type = ptr_type {.base: Base, .d: i32, .e: i32} [template]
42+
// CHECK:STDOUT: }
43+
// CHECK:STDOUT:
44+
// CHECK:STDOUT: file {
45+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
46+
// CHECK:STDOUT: .Base = %Base.decl
47+
// CHECK:STDOUT: .Derived = %Derived.decl
48+
// CHECK:STDOUT: .AccessDerived = %AccessDerived
49+
// CHECK:STDOUT: .AccessBase = %AccessBase
50+
// CHECK:STDOUT: }
51+
// CHECK:STDOUT: %Base.decl: type = class_decl @Base [template = constants.%Base] {}
52+
// CHECK:STDOUT: %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {}
53+
// CHECK:STDOUT: %AccessDerived: <function> = fn_decl @AccessDerived [template] {
54+
// CHECK:STDOUT: %Derived.ref.loc20: type = name_ref Derived, %Derived.decl [template = constants.%Derived]
55+
// CHECK:STDOUT: %d.loc20_18.1: Derived = param d
56+
// CHECK:STDOUT: @AccessDerived.%d: Derived = bind_name d, %d.loc20_18.1
57+
// CHECK:STDOUT: %return.var.loc20: ref i32 = var <return slot>
58+
// CHECK:STDOUT: }
59+
// CHECK:STDOUT: %AccessBase: <function> = fn_decl @AccessBase [template] {
60+
// CHECK:STDOUT: %Derived.ref.loc24: type = name_ref Derived, %Derived.decl [template = constants.%Derived]
61+
// CHECK:STDOUT: %d.loc24_15.1: Derived = param d
62+
// CHECK:STDOUT: @AccessBase.%d: Derived = bind_name d, %d.loc24_15.1
63+
// CHECK:STDOUT: %return.var.loc24: ref i32 = var <return slot>
64+
// CHECK:STDOUT: }
65+
// CHECK:STDOUT: }
66+
// CHECK:STDOUT:
67+
// CHECK:STDOUT: class @Base {
68+
// CHECK:STDOUT: %.loc8: <unbound element of class Base> = field_decl a, element0 [template]
69+
// CHECK:STDOUT: %.loc9: <unbound element of class Base> = field_decl b, element1 [template]
70+
// CHECK:STDOUT: %.loc10: <unbound element of class Base> = field_decl c, element2 [template]
71+
// CHECK:STDOUT:
72+
// CHECK:STDOUT: !members:
73+
// CHECK:STDOUT: .Self = constants.%Base
74+
// CHECK:STDOUT: .a = %.loc8
75+
// CHECK:STDOUT: .b = %.loc9
76+
// CHECK:STDOUT: .c = %.loc10
77+
// CHECK:STDOUT: }
78+
// CHECK:STDOUT:
79+
// CHECK:STDOUT: class @Derived {
80+
// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
81+
// CHECK:STDOUT: %.loc14: <unbound element of class Derived> = base_decl Base, element0 [template]
82+
// CHECK:STDOUT: %.loc16: <unbound element of class Derived> = field_decl d, element1 [template]
83+
// CHECK:STDOUT: %.loc17: <unbound element of class Derived> = field_decl e, element2 [template]
84+
// CHECK:STDOUT:
85+
// CHECK:STDOUT: !members:
86+
// CHECK:STDOUT: .Self = constants.%Derived
87+
// CHECK:STDOUT: .base = %.loc14
88+
// CHECK:STDOUT: .d = %.loc16
89+
// CHECK:STDOUT: .e = %.loc17
90+
// CHECK:STDOUT: extend name_scope1
91+
// CHECK:STDOUT: }
92+
// CHECK:STDOUT:
93+
// CHECK:STDOUT: fn @AccessDerived(%d: Derived) -> i32 {
94+
// CHECK:STDOUT: !entry:
95+
// CHECK:STDOUT: %d.ref.loc21_10: Derived = name_ref d, %d
96+
// CHECK:STDOUT: %Derived.ref: type = name_ref Derived, file.%Derived.decl [template = constants.%Derived]
97+
// CHECK:STDOUT: %d.ref.loc21_20: <unbound element of class Derived> = name_ref d, @Derived.%.loc16 [template = @Derived.%.loc16]
98+
// CHECK:STDOUT: %.loc21_11.1: ref i32 = class_element_access %d.ref.loc21_10, element1
99+
// CHECK:STDOUT: %.loc21_11.2: i32 = bind_value %.loc21_11.1
100+
// CHECK:STDOUT: return %.loc21_11.2
101+
// CHECK:STDOUT: }
102+
// CHECK:STDOUT:
103+
// CHECK:STDOUT: fn @AccessBase(%d: Derived) -> i32 {
104+
// CHECK:STDOUT: !entry:
105+
// CHECK:STDOUT: %d.ref: Derived = name_ref d, %d
106+
// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
107+
// CHECK:STDOUT: %b.ref: <unbound element of class Base> = name_ref b, @Base.%.loc9 [template = @Base.%.loc9]
108+
// CHECK:STDOUT: %.loc25_11.1: ref Base = class_element_access %d.ref, element0
109+
// CHECK:STDOUT: %.loc25_10: ref Base = converted %d.ref, %.loc25_11.1
110+
// CHECK:STDOUT: %.loc25_11.2: ref i32 = class_element_access %.loc25_10, element1
111+
// CHECK:STDOUT: %.loc25_11.3: i32 = bind_value %.loc25_11.2
112+
// CHECK:STDOUT: return %.loc25_11.3
113+
// CHECK:STDOUT: }
114+
// CHECK:STDOUT:
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
// AUTOUPDATE
6+
7+
class A {
8+
var a: i32;
9+
}
10+
11+
class B {
12+
var b: i32;
13+
}
14+
15+
fn AccessBInA(a: A) -> i32 {
16+
// CHECK:STDERR: fail_compound_type_mismatch.carbon:[[@LINE+3]]:10: ERROR: Cannot implicitly convert from `A` to `B`.
17+
// CHECK:STDERR: return a.(B.b);
18+
// CHECK:STDERR: ^~~~~~~
19+
return a.(B.b);
20+
}
21+
22+
// CHECK:STDOUT: --- fail_compound_type_mismatch.carbon
23+
// CHECK:STDOUT:
24+
// CHECK:STDOUT: constants {
25+
// CHECK:STDOUT: %A: type = class_type @A [template]
26+
// CHECK:STDOUT: %.1: type = unbound_element_type A, i32 [template]
27+
// CHECK:STDOUT: %.2: type = struct_type {.a: i32} [template]
28+
// CHECK:STDOUT: %B: type = class_type @B [template]
29+
// CHECK:STDOUT: %.3: type = unbound_element_type B, i32 [template]
30+
// CHECK:STDOUT: %.4: type = struct_type {.b: i32} [template]
31+
// CHECK:STDOUT: %.5: type = ptr_type {.a: i32} [template]
32+
// CHECK:STDOUT: %.6: type = ptr_type {.b: i32} [template]
33+
// CHECK:STDOUT: }
34+
// CHECK:STDOUT:
35+
// CHECK:STDOUT: file {
36+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
37+
// CHECK:STDOUT: .A = %A.decl
38+
// CHECK:STDOUT: .B = %B.decl
39+
// CHECK:STDOUT: .AccessBInA = %AccessBInA
40+
// CHECK:STDOUT: }
41+
// CHECK:STDOUT: %A.decl: type = class_decl @A [template = constants.%A] {}
42+
// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {}
43+
// CHECK:STDOUT: %AccessBInA: <function> = fn_decl @AccessBInA [template] {
44+
// CHECK:STDOUT: %A.ref: type = name_ref A, %A.decl [template = constants.%A]
45+
// CHECK:STDOUT: %a.loc15_15.1: A = param a
46+
// CHECK:STDOUT: @AccessBInA.%a: A = bind_name a, %a.loc15_15.1
47+
// CHECK:STDOUT: %return.var: ref i32 = var <return slot>
48+
// CHECK:STDOUT: }
49+
// CHECK:STDOUT: }
50+
// CHECK:STDOUT:
51+
// CHECK:STDOUT: class @A {
52+
// CHECK:STDOUT: %.loc8: <unbound element of class A> = field_decl a, element0 [template]
53+
// CHECK:STDOUT:
54+
// CHECK:STDOUT: !members:
55+
// CHECK:STDOUT: .Self = constants.%A
56+
// CHECK:STDOUT: .a = %.loc8
57+
// CHECK:STDOUT: }
58+
// CHECK:STDOUT:
59+
// CHECK:STDOUT: class @B {
60+
// CHECK:STDOUT: %.loc12: <unbound element of class B> = field_decl b, element0 [template]
61+
// CHECK:STDOUT:
62+
// CHECK:STDOUT: !members:
63+
// CHECK:STDOUT: .Self = constants.%B
64+
// CHECK:STDOUT: .b = %.loc12
65+
// CHECK:STDOUT: }
66+
// CHECK:STDOUT:
67+
// CHECK:STDOUT: fn @AccessBInA(%a: A) -> i32 {
68+
// CHECK:STDOUT: !entry:
69+
// CHECK:STDOUT: %a.ref: A = name_ref a, %a
70+
// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
71+
// CHECK:STDOUT: %b.ref: <unbound element of class B> = name_ref b, @B.%.loc12 [template = @B.%.loc12]
72+
// CHECK:STDOUT: %.loc19: i32 = class_element_access <error>, element0 [template = <error>]
73+
// CHECK:STDOUT: return <error>
74+
// CHECK:STDOUT: }
75+
// CHECK:STDOUT:

0 commit comments

Comments
 (0)