From 3884d3c27e34ecf1c3d9c471cafd5f79b68e45f2 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Sat, 16 Mar 2024 15:38:23 -0700 Subject: [PATCH] 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. --- toolchain/check/handle_name.cpp | 17 ++- toolchain/check/member_access.cpp | 42 +++++++ toolchain/check/member_access.h | 8 ++ .../testdata/class/compound_field.carbon | 114 +++++++++++++++++ .../class/fail_compound_type_mismatch.carbon | 75 +++++++++++ toolchain/check/testdata/impl/compound.carbon | 118 ++++++++++++++++++ .../testdata/impl/fail_call_invalid.carbon | 87 +++++++++++++ .../fail_redundant_compound_access.carbon | 63 ++++++++++ toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/lower/testdata/impl/impl.carbon | 37 ++++++ toolchain/parse/handle_period.cpp | 26 +++- toolchain/parse/node_ids.h | 2 + toolchain/parse/node_kind.h | 11 +- toolchain/parse/state.def | 34 ++++- .../impl/fail_out_of_line_member.carbon | 93 ++++++++++++++ .../testdata/member_access/compound.carbon | 34 +++++ .../member_access/fail_keyword.carbon | 68 ++++++++++ .../testdata/member_access/keyword.carbon | 30 +++++ .../testdata/member_access/simple.carbon | 47 +++++++ .../testdata/struct/fail_dot_paren.carbon | 26 ++++ toolchain/parse/typed_nodes.h | 11 +- 21 files changed, 924 insertions(+), 20 deletions(-) create mode 100644 toolchain/check/testdata/class/compound_field.carbon create mode 100644 toolchain/check/testdata/class/fail_compound_type_mismatch.carbon create mode 100644 toolchain/check/testdata/impl/compound.carbon create mode 100644 toolchain/check/testdata/impl/fail_call_invalid.carbon create mode 100644 toolchain/check/testdata/operators/fail_redundant_compound_access.carbon create mode 100644 toolchain/lower/testdata/impl/impl.carbon create mode 100644 toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon create mode 100644 toolchain/parse/testdata/member_access/compound.carbon create mode 100644 toolchain/parse/testdata/member_access/fail_keyword.carbon create mode 100644 toolchain/parse/testdata/member_access/keyword.carbon create mode 100644 toolchain/parse/testdata/member_access/simple.carbon create mode 100644 toolchain/parse/testdata/struct/fail_dot_paren.carbon diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 62e2b6556e44..5ee800852d08 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -5,6 +5,7 @@ #include "toolchain/check/context.h" #include "toolchain/check/member_access.h" #include "toolchain/lex/token_kind.h" +#include "toolchain/parse/typed_nodes.h" #include "toolchain/sem_ir/inst.h" #include "toolchain/sem_ir/typed_insts.h" @@ -12,10 +13,18 @@ namespace Carbon::Check { auto HandleMemberAccessExpr(Context& context, Parse::MemberAccessExprId node_id) -> bool { - SemIR::NameId name_id = context.node_stack().PopName(); - auto base_id = context.node_stack().PopExpr(); - auto member_id = PerformMemberAccess(context, node_id, base_id, name_id); - context.node_stack().Push(node_id, member_id); + if (context.node_stack().PeekIs()) { + auto member_expr_id = context.node_stack().PopExpr(); + auto base_id = context.node_stack().PopExpr(); + auto member_id = + PerformCompoundMemberAccess(context, node_id, base_id, member_expr_id); + context.node_stack().Push(node_id, member_id); + } else { + SemIR::NameId name_id = context.node_stack().PopName(); + auto base_id = context.node_stack().PopExpr(); + auto member_id = PerformMemberAccess(context, node_id, base_id, name_id); + context.node_stack().Push(node_id, member_id); + } return true; } diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 1a8963e2dd5d..e8f0f9fe1c26 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -269,6 +269,10 @@ static auto PerformInstanceBinding(Context& context, context.GetBuiltinType(SemIR::BuiltinKind::FunctionType)) { // Find the named function and check whether it's an instance method. auto function_name_id = context.constant_values().Get(member_id); + if (function_name_id == SemIR::ConstantId::Error) { + return SemIR::InstId::BuiltinError; + } + CARBON_CHECK(function_name_id.is_constant()) << "Non-constant value " << context.insts().Get(member_id) << " of function type"; @@ -366,4 +370,42 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, return member_id; } +auto PerformCompoundMemberAccess(Context& context, + Parse::MemberAccessExprId node_id, + SemIR::InstId base_id, + SemIR::InstId member_expr_id) + -> SemIR::InstId { + // Materialize a temporary for the base expression if necessary. + base_id = ConvertToValueOrRefExpr(context, base_id); + auto base_type_id = context.insts().Get(base_id).type_id(); + auto base_type_const_id = context.types().GetConstantId(base_type_id); + + auto member_id = member_expr_id; + auto member = context.insts().Get(member_id); + + // If the member expression names an associated entity, impl lookup is always + // performed using the type of the base expression. + if (auto assoc_type = context.types().TryGetAs( + member.type_id())) { + member_id = + PerformImplLookup(context, base_type_const_id, *assoc_type, member_id); + } + + // Perform instance binding if we found an instance member. + member_id = PerformInstanceBinding(context, node_id, base_id, member_id); + + // If we didn't perform impl lookup or instance binding, that's an error + // because the base expression is not used for anything. + if (member_id == member_expr_id) { + CARBON_DIAGNOSTIC(CompoundMemberAccessDoesNotUseBase, Error, + "Member name of type `{0}` in compound member access is " + "not an instance member or an interface member.", + SemIR::TypeId); + context.emitter().Emit(node_id, CompoundMemberAccessDoesNotUseBase, + member.type_id()); + } + + return member_id; +} + } // namespace Carbon::Check diff --git a/toolchain/check/member_access.h b/toolchain/check/member_access.h index cb84edcec0f3..bd3cf59b08fb 100644 --- a/toolchain/check/member_access.h +++ b/toolchain/check/member_access.h @@ -16,6 +16,14 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, SemIR::InstId base_id, SemIR::NameId name_id) -> SemIR::InstId; +// Creates SemIR to perform a compound member access with base expression +// `base_id` and member name expression `member_expr_id`. Returns the result of +// the access. +auto PerformCompoundMemberAccess(Context& context, + Parse::MemberAccessExprId node_id, + SemIR::InstId base_id, + SemIR::InstId member_expr_id) -> SemIR::InstId; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_MEMBER_ACCESS_H_ diff --git a/toolchain/check/testdata/class/compound_field.carbon b/toolchain/check/testdata/class/compound_field.carbon new file mode 100644 index 000000000000..f298475b2392 --- /dev/null +++ b/toolchain/check/testdata/class/compound_field.carbon @@ -0,0 +1,114 @@ +// 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 + +base class Base { + var a: i32; + var b: i32; + var c: i32; +} + +class Derived { + extend base: Base; + + var d: i32; + var e: i32; +} + +fn AccessDerived(d: Derived) -> i32 { + return d.(Derived.d); +} + +fn AccessBase(d: Derived) -> i32 { + return d.(Base.b); +} + +// CHECK:STDOUT: --- compound_field.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Base: type = class_type @Base [template] +// CHECK:STDOUT: %.1: type = unbound_element_type Base, i32 [template] +// CHECK:STDOUT: %.2: type = struct_type {.a: i32, .b: i32, .c: i32} [template] +// CHECK:STDOUT: %Derived: type = class_type @Derived [template] +// CHECK:STDOUT: %.3: type = ptr_type {.a: i32, .b: i32, .c: i32} [template] +// CHECK:STDOUT: %.4: type = unbound_element_type Derived, Base [template] +// CHECK:STDOUT: %.5: type = unbound_element_type Derived, i32 [template] +// CHECK:STDOUT: %.6: type = struct_type {.base: Base, .d: i32, .e: i32} [template] +// CHECK:STDOUT: %.7: type = struct_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} [template] +// CHECK:STDOUT: %.8: type = ptr_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} [template] +// CHECK:STDOUT: %.9: type = ptr_type {.base: Base, .d: i32, .e: i32} [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Base = %Base.decl +// CHECK:STDOUT: .Derived = %Derived.decl +// CHECK:STDOUT: .AccessDerived = %AccessDerived +// CHECK:STDOUT: .AccessBase = %AccessBase +// CHECK:STDOUT: } +// CHECK:STDOUT: %Base.decl: type = class_decl @Base [template = constants.%Base] {} +// CHECK:STDOUT: %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} +// CHECK:STDOUT: %AccessDerived: = fn_decl @AccessDerived [template] { +// CHECK:STDOUT: %Derived.ref.loc20: type = name_ref Derived, %Derived.decl [template = constants.%Derived] +// CHECK:STDOUT: %d.loc20_18.1: Derived = param d +// CHECK:STDOUT: @AccessDerived.%d: Derived = bind_name d, %d.loc20_18.1 +// CHECK:STDOUT: %return.var.loc20: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %AccessBase: = fn_decl @AccessBase [template] { +// CHECK:STDOUT: %Derived.ref.loc24: type = name_ref Derived, %Derived.decl [template = constants.%Derived] +// CHECK:STDOUT: %d.loc24_15.1: Derived = param d +// CHECK:STDOUT: @AccessBase.%d: Derived = bind_name d, %d.loc24_15.1 +// CHECK:STDOUT: %return.var.loc24: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %.loc8: = field_decl a, element0 [template] +// CHECK:STDOUT: %.loc9: = field_decl b, element1 [template] +// CHECK:STDOUT: %.loc10: = field_decl c, element2 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Base +// CHECK:STDOUT: .a = %.loc8 +// CHECK:STDOUT: .b = %.loc9 +// CHECK:STDOUT: .c = %.loc10 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] +// CHECK:STDOUT: %.loc14: = base_decl Base, element0 [template] +// CHECK:STDOUT: %.loc16: = field_decl d, element1 [template] +// CHECK:STDOUT: %.loc17: = field_decl e, element2 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Derived +// CHECK:STDOUT: .base = %.loc14 +// CHECK:STDOUT: .d = %.loc16 +// CHECK:STDOUT: .e = %.loc17 +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessDerived(%d: Derived) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %d.ref.loc21_10: Derived = name_ref d, %d +// CHECK:STDOUT: %Derived.ref: type = name_ref Derived, file.%Derived.decl [template = constants.%Derived] +// CHECK:STDOUT: %d.ref.loc21_20: = name_ref d, @Derived.%.loc16 [template = @Derived.%.loc16] +// CHECK:STDOUT: %.loc21_11.1: ref i32 = class_element_access %d.ref.loc21_10, element1 +// CHECK:STDOUT: %.loc21_11.2: i32 = bind_value %.loc21_11.1 +// CHECK:STDOUT: return %.loc21_11.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessBase(%d: Derived) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %d.ref: Derived = name_ref d, %d +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] +// CHECK:STDOUT: %b.ref: = name_ref b, @Base.%.loc9 [template = @Base.%.loc9] +// CHECK:STDOUT: %.loc25_11.1: ref Base = class_element_access %d.ref, element0 +// CHECK:STDOUT: %.loc25_10: ref Base = converted %d.ref, %.loc25_11.1 +// CHECK:STDOUT: %.loc25_11.2: ref i32 = class_element_access %.loc25_10, element1 +// CHECK:STDOUT: %.loc25_11.3: i32 = bind_value %.loc25_11.2 +// CHECK:STDOUT: return %.loc25_11.3 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon b/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon new file mode 100644 index 000000000000..84ab9b8de858 --- /dev/null +++ b/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon @@ -0,0 +1,75 @@ +// 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 A { + var a: i32; +} + +class B { + var b: i32; +} + +fn AccessBInA(a: A) -> i32 { + // CHECK:STDERR: fail_compound_type_mismatch.carbon:[[@LINE+3]]:10: ERROR: Cannot implicitly convert from `A` to `B`. + // CHECK:STDERR: return a.(B.b); + // CHECK:STDERR: ^~~~~~~ + return a.(B.b); +} + +// CHECK:STDOUT: --- fail_compound_type_mismatch.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %A: type = class_type @A [template] +// CHECK:STDOUT: %.1: type = unbound_element_type A, i32 [template] +// CHECK:STDOUT: %.2: type = struct_type {.a: i32} [template] +// CHECK:STDOUT: %B: type = class_type @B [template] +// CHECK:STDOUT: %.3: type = unbound_element_type B, i32 [template] +// CHECK:STDOUT: %.4: type = struct_type {.b: i32} [template] +// CHECK:STDOUT: %.5: type = ptr_type {.a: i32} [template] +// CHECK:STDOUT: %.6: type = ptr_type {.b: i32} [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .A = %A.decl +// CHECK:STDOUT: .B = %B.decl +// CHECK:STDOUT: .AccessBInA = %AccessBInA +// CHECK:STDOUT: } +// CHECK:STDOUT: %A.decl: type = class_decl @A [template = constants.%A] {} +// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} +// CHECK:STDOUT: %AccessBInA: = fn_decl @AccessBInA [template] { +// CHECK:STDOUT: %A.ref: type = name_ref A, %A.decl [template = constants.%A] +// CHECK:STDOUT: %a.loc15_15.1: A = param a +// CHECK:STDOUT: @AccessBInA.%a: A = bind_name a, %a.loc15_15.1 +// CHECK:STDOUT: %return.var: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @A { +// CHECK:STDOUT: %.loc8: = field_decl a, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%A +// CHECK:STDOUT: .a = %.loc8 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: %.loc12: = field_decl b, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%B +// CHECK:STDOUT: .b = %.loc12 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessBInA(%a: A) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.ref: A = name_ref a, %a +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B] +// CHECK:STDOUT: %b.ref: = name_ref b, @B.%.loc12 [template = @B.%.loc12] +// CHECK:STDOUT: %.loc19: i32 = class_element_access , element0 [template = ] +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/compound.carbon b/toolchain/check/testdata/impl/compound.carbon new file mode 100644 index 000000000000..6fbfa1203e26 --- /dev/null +++ b/toolchain/check/testdata/impl/compound.carbon @@ -0,0 +1,118 @@ +// 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 + +interface Simple { + fn F(); + fn G[self: Self](); +} + +impl i32 as Simple { + fn F(); + fn G[self: i32](); +} + +fn NonInstanceCall(n: i32) { + n.(Simple.F)(); +} + +fn InstanceCall(n: i32) { + n.(Simple.G)(); +} + +// CHECK:STDOUT: --- compound.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: type = interface_type @Simple [template] +// CHECK:STDOUT: %.2: type = assoc_entity_type @Simple, [template] +// CHECK:STDOUT: %.3: in Simple> = assoc_entity element0, @Simple.%F [template] +// CHECK:STDOUT: %.4: in Simple> = assoc_entity element1, @Simple.%G [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.%F, @impl.%G) [template] +// CHECK:STDOUT: %.6: type = tuple_type () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Simple = %Simple.decl +// CHECK:STDOUT: .NonInstanceCall = %NonInstanceCall +// CHECK:STDOUT: .InstanceCall = %InstanceCall +// CHECK:STDOUT: } +// CHECK:STDOUT: %Simple.decl: type = interface_decl @Simple [template = constants.%.1] {} +// CHECK:STDOUT: impl_decl @impl { +// CHECK:STDOUT: %Simple.ref: type = name_ref Simple, %Simple.decl [template = constants.%.1] +// CHECK:STDOUT: } +// CHECK:STDOUT: %NonInstanceCall: = fn_decl @NonInstanceCall [template] { +// CHECK:STDOUT: %n.loc17_20.1: i32 = param n +// CHECK:STDOUT: @NonInstanceCall.%n: i32 = bind_name n, %n.loc17_20.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %InstanceCall: = fn_decl @InstanceCall [template] { +// CHECK:STDOUT: %n.loc21_17.1: i32 = param n +// CHECK:STDOUT: @InstanceCall.%n: i32 = bind_name n, %n.loc21_17.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @Simple { +// CHECK:STDOUT: %Self: Simple = bind_symbolic_name Self [symbolic] +// CHECK:STDOUT: %F: = fn_decl @F.1 [template] {} +// CHECK:STDOUT: %.loc8: in Simple> = assoc_entity element0, %F [template = constants.%.3] +// CHECK:STDOUT: %G: = fn_decl @G.1 [template] { +// CHECK:STDOUT: %Self.ref: Simple = name_ref Self, %Self [symbolic = %Self] +// CHECK:STDOUT: %.loc9_14.1: type = facet_type_access %Self.ref [symbolic = %Self] +// CHECK:STDOUT: %.loc9_14.2: type = converted %Self.ref, %.loc9_14.1 [symbolic = %Self] +// CHECK:STDOUT: %self.loc9_8.1: Self = param self +// CHECK:STDOUT: %self.loc9_8.2: Self = bind_name self, %self.loc9_8.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc9_21: in Simple> = assoc_entity element1, %G [template = constants.%.4] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: .F = %.loc8 +// CHECK:STDOUT: .G = %.loc9_21 +// CHECK:STDOUT: witness = (%F, %G) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: i32 as Simple { +// CHECK:STDOUT: %F: = fn_decl @F.2 [template] {} +// CHECK:STDOUT: %G: = fn_decl @G.2 [template] { +// CHECK:STDOUT: %self.loc14_8.1: i32 = param self +// CHECK:STDOUT: %self.loc14_8.2: i32 = bind_name self, %self.loc14_8.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.1: = interface_witness (%F, %G) [template = constants.%.5] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: witness = %.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G.1[@Simple.%self.loc9_8.2: Self](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G.2[@impl.%self.loc14_8.2: i32](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @NonInstanceCall(%n: i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_ref n, %n +// CHECK:STDOUT: %Simple.ref: type = name_ref Simple, file.%Simple.decl [template = constants.%.1] +// CHECK:STDOUT: %F.ref: in Simple> = name_ref F, @Simple.%.loc8 [template = constants.%.3] +// CHECK:STDOUT: %.1: = interface_witness_access @impl.%.1, element0 [template = @impl.%F] +// CHECK:STDOUT: %.loc18: init () = call %.1() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @InstanceCall(%n: i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_ref n, %n +// CHECK:STDOUT: %Simple.ref: type = name_ref Simple, file.%Simple.decl [template = constants.%.1] +// CHECK:STDOUT: %G.ref: in Simple> = name_ref G, @Simple.%.loc9_21 [template = constants.%.4] +// CHECK:STDOUT: %.1: = interface_witness_access @impl.%.1, element1 [template = @impl.%G] +// CHECK:STDOUT: %.loc22_4: = bound_method %n.ref, %.1 +// CHECK:STDOUT: %.loc22_15: init () = call %.loc22_4(%n.ref) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/fail_call_invalid.carbon b/toolchain/check/testdata/impl/fail_call_invalid.carbon new file mode 100644 index 000000000000..6bda7c20c28f --- /dev/null +++ b/toolchain/check/testdata/impl/fail_call_invalid.carbon @@ -0,0 +1,87 @@ +// 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 + +interface Simple { + fn G[self: Self](); +} + +impl i32 as Simple { + // CHECK:STDERR: fail_call_invalid.carbon:[[@LINE+3]]:14: ERROR: Name `Undeclared` not found. + // CHECK:STDERR: fn G[self: Undeclared](); + // CHECK:STDERR: ^~~~~~~~~~ + fn G[self: Undeclared](); +} + +fn InstanceCall(n: i32) { + n.(Simple.G)(); +} + +// CHECK:STDOUT: --- fail_call_invalid.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: type = interface_type @Simple [template] +// CHECK:STDOUT: %.2: type = assoc_entity_type @Simple, [template] +// CHECK:STDOUT: %.3: in Simple> = assoc_entity element0, @Simple.%G [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Simple = %Simple.decl +// CHECK:STDOUT: .InstanceCall = %InstanceCall +// CHECK:STDOUT: } +// CHECK:STDOUT: %Simple.decl: type = interface_decl @Simple [template = constants.%.1] {} +// CHECK:STDOUT: impl_decl @impl { +// CHECK:STDOUT: %Simple.ref: type = name_ref Simple, %Simple.decl [template = constants.%.1] +// CHECK:STDOUT: } +// CHECK:STDOUT: %InstanceCall: = fn_decl @InstanceCall [template] { +// CHECK:STDOUT: %n.loc18_17.1: i32 = param n +// CHECK:STDOUT: @InstanceCall.%n: i32 = bind_name n, %n.loc18_17.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @Simple { +// CHECK:STDOUT: %Self: Simple = bind_symbolic_name Self [symbolic] +// CHECK:STDOUT: %G: = fn_decl @G.1 [template] { +// CHECK:STDOUT: %Self.ref: Simple = name_ref Self, %Self [symbolic = %Self] +// CHECK:STDOUT: %.loc8_14.1: type = facet_type_access %Self.ref [symbolic = %Self] +// CHECK:STDOUT: %.loc8_14.2: type = converted %Self.ref, %.loc8_14.1 [symbolic = %Self] +// CHECK:STDOUT: %self.loc8_8.1: Self = param self +// CHECK:STDOUT: %self.loc8_8.2: Self = bind_name self, %self.loc8_8.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc8_21: in Simple> = assoc_entity element0, %G [template = constants.%.3] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: .G = %.loc8_21 +// CHECK:STDOUT: witness = (%G) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: i32 as Simple { +// CHECK:STDOUT: %G: = fn_decl @G.2 [template] { +// CHECK:STDOUT: %Undeclared.ref: = name_ref Undeclared, [template = ] +// CHECK:STDOUT: %self.loc15_8.1: = param self +// CHECK:STDOUT: %self.loc15_8.2: = bind_name self, %self.loc15_8.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.1: = interface_witness () [template = ] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: witness = %.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G.1[@Simple.%self.loc8_8.2: Self](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G.2[@impl.%self.loc15_8.2: ](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @InstanceCall(%n: i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_ref n, %n +// CHECK:STDOUT: %Simple.ref: type = name_ref Simple, file.%Simple.decl [template = constants.%.1] +// CHECK:STDOUT: %G.ref: in Simple> = name_ref G, @Simple.%.loc8_21 [template = constants.%.3] +// CHECK:STDOUT: %.1: = interface_witness_access @impl.%.1, element0 [template = ] +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/fail_redundant_compound_access.carbon b/toolchain/check/testdata/operators/fail_redundant_compound_access.carbon new file mode 100644 index 000000000000..9c821cd59b9c --- /dev/null +++ b/toolchain/check/testdata/operators/fail_redundant_compound_access.carbon @@ -0,0 +1,63 @@ +// 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 + +fn F() -> i32 { return 0; } + +fn Main() { + var a: i32 = 3; + // CHECK:STDERR: fail_redundant_compound_access.carbon:[[@LINE+3]]:7: ERROR: Member name of type `i32` in compound member access is not an instance member or an interface member. + // CHECK:STDERR: a = a.(a); + // CHECK:STDERR: ^~~~~ + a = a.(a); + // CHECK:STDERR: fail_redundant_compound_access.carbon:[[@LINE+3]]:7: ERROR: Member name of type `` in compound member access is not an instance member or an interface member. + // CHECK:STDERR: a = a.(F)(); + // CHECK:STDERR: ^~~~~ + a = a.(F)(); +} + +// CHECK:STDOUT: --- fail_redundant_compound_access.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: i32 = int_literal 0 [template] +// CHECK:STDOUT: %.2: i32 = int_literal 3 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .Main = %Main +// CHECK:STDOUT: } +// CHECK:STDOUT: %F: = fn_decl @F [template] { +// CHECK:STDOUT: %return.var: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %Main: = fn_decl @Main [template] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc7: i32 = int_literal 0 [template = constants.%.1] +// CHECK:STDOUT: return %.loc7 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Main() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.var: ref i32 = var a +// CHECK:STDOUT: %a: ref i32 = bind_name a, %a.var +// CHECK:STDOUT: %.loc10: i32 = int_literal 3 [template = constants.%.2] +// CHECK:STDOUT: assign %a.var, %.loc10 +// CHECK:STDOUT: %a.ref.loc14_3: ref i32 = name_ref a, %a +// CHECK:STDOUT: %a.ref.loc14_7: ref i32 = name_ref a, %a +// CHECK:STDOUT: %a.ref.loc14_10: ref i32 = name_ref a, %a +// CHECK:STDOUT: %.loc14: i32 = bind_value %a.ref.loc14_10 +// CHECK:STDOUT: assign %a.ref.loc14_3, %.loc14 +// CHECK:STDOUT: %a.ref.loc18_3: ref i32 = name_ref a, %a +// CHECK:STDOUT: %a.ref.loc18_7: ref i32 = name_ref a, %a +// CHECK:STDOUT: %F.ref: = name_ref F, file.%F [template = file.%F] +// CHECK:STDOUT: %.loc18: init i32 = call %F.ref() +// CHECK:STDOUT: assign %a.ref.loc18_3, %.loc18 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index b5246b32c513..0a21be17f928 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -234,6 +234,7 @@ CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType) CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer) CARBON_DIAGNOSTIC_KIND(DerefOfType) CARBON_DIAGNOSTIC_KIND(CompileTimeBindingInVarDecl) +CARBON_DIAGNOSTIC_KIND(CompoundMemberAccessDoesNotUseBase) CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend) CARBON_DIAGNOSTIC_KIND(NameNotFound) CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate) diff --git a/toolchain/lower/testdata/impl/impl.carbon b/toolchain/lower/testdata/impl/impl.carbon new file mode 100644 index 000000000000..683c257f0fc3 --- /dev/null +++ b/toolchain/lower/testdata/impl/impl.carbon @@ -0,0 +1,37 @@ +// 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 + +interface I { + fn F[self: Self]() -> i32; +} + +class A { + var n: i32; +} + +impl A as I { + fn F[self: A]() -> i32 { + return self.n; + } +} + +fn Call(a: A) -> i32 { + return a.(I.F)(); +} + +// CHECK:STDOUT: ; ModuleID = 'impl.carbon' +// CHECK:STDOUT: source_filename = "impl.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @F(ptr %self) { +// CHECK:STDOUT: %n = getelementptr inbounds { i32 }, ptr %self, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %n, align 4 +// CHECK:STDOUT: ret i32 %1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @Call(ptr %a) { +// CHECK:STDOUT: %F = call i32 @F(ptr %a) +// CHECK:STDOUT: ret i32 %F +// CHECK:STDOUT: } diff --git a/toolchain/parse/handle_period.cpp b/toolchain/parse/handle_period.cpp index 1b463db1f56a..c515fdd2e3a7 100644 --- a/toolchain/parse/handle_period.cpp +++ b/toolchain/parse/handle_period.cpp @@ -10,7 +10,7 @@ namespace Carbon::Parse { // TODO: This currently only supports identifiers on the rhs, but will in the // future need to handle things like `object.(Interface.member)` for qualifiers. static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, - bool is_arrow) -> void { + State paren_state, bool is_arrow) -> void { auto state = context.PopState(); // We're handling `.something` or `->something`. @@ -25,6 +25,12 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, NodeKind::BaseName)) { // OK, `.base`. This is allowed in any name context other than declaring a // new qualified name: `fn Namespace.base() {}` + } else if (paren_state != State::Invalid && + context.PositionIs(Lex::TokenKind::OpenParen)) { + state.state = paren_state; + context.PushState(state); + context.PushState(State::ParenExpr); + return; } else { CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDotOrArrow, Error, "Expected identifier after `{0}`.", llvm::StringLiteral); @@ -49,23 +55,37 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, } auto HandlePeriodAsDecl(Context& context) -> void { - HandlePeriodOrArrow(context, NodeKind::QualifiedName, + HandlePeriodOrArrow(context, NodeKind::QualifiedName, State::Invalid, /*is_arrow=*/false); } auto HandlePeriodAsExpr(Context& context) -> void { HandlePeriodOrArrow(context, NodeKind::MemberAccessExpr, + State::CompoundMemberAccess, /*is_arrow=*/false); } auto HandlePeriodAsStruct(Context& context) -> void { - HandlePeriodOrArrow(context, NodeKind::StructFieldDesignator, + HandlePeriodOrArrow(context, NodeKind::StructFieldDesignator, State::Invalid, /*is_arrow=*/false); } auto HandleArrowExpr(Context& context) -> void { HandlePeriodOrArrow(context, NodeKind::PointerMemberAccessExpr, + State::CompoundPointerMemberAccess, /*is_arrow=*/true); } +auto HandleCompoundMemberAccess(Context& context) -> void { + auto state = context.PopState(); + context.AddNode(NodeKind::MemberAccessExpr, state.token, state.subtree_start, + state.has_error); +} + +auto HandleCompoundPointerMemberAccess(Context& context) -> void { + auto state = context.PopState(); + context.AddNode(NodeKind::PointerMemberAccessExpr, state.token, + state.subtree_start, state.has_error); +} + } // namespace Carbon::Parse diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h index a2c70b6104a9..1196e22e2134 100644 --- a/toolchain/parse/node_ids.h +++ b/toolchain/parse/node_ids.h @@ -66,6 +66,8 @@ using AnyDeclId = NodeIdInCategory; using AnyExprId = NodeIdInCategory; using AnyImplAsId = NodeIdInCategory; using AnyMemberNameId = NodeIdInCategory; +using AnyMemberNameOrMemberExprId = + NodeIdInCategory; using AnyModifierId = NodeIdInCategory; using AnyNameComponentId = NodeIdInCategory; using AnyPatternId = NodeIdInCategory; diff --git a/toolchain/parse/node_kind.h b/toolchain/parse/node_kind.h index 37b7c1d9c279..369c37b40e82 100644 --- a/toolchain/parse/node_kind.h +++ b/toolchain/parse/node_kind.h @@ -23,11 +23,12 @@ enum class NodeCategory : uint32_t { Decl = 1 << 0, Expr = 1 << 1, ImplAs = 1 << 2, - MemberName = 1 << 3, - Modifier = 1 << 4, - NameComponent = 1 << 5, - Pattern = 1 << 6, - Statement = 1 << 7, + MemberExpr = 1 << 3, + MemberName = 1 << 4, + Modifier = 1 << 5, + NameComponent = 1 << 6, + Pattern = 1 << 7, + Statement = 1 << 8, None = 0, LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Statement) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index afa9ebd3e002..aecb84c565d2 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -385,13 +385,41 @@ CARBON_PARSE_STATE(DeclScopeLoop) // // . name // ^~~~~~ +// -> name +// ^~~~~~~ +// . base (variant is not Decl) +// ^~~~~~ +// -> base (variant is not Decl) +// ^~~~~~~ +// . ??? (??? consumed if it is a keyword) +// ^ +// -> ??? (??? consumed if it is a keyword) +// ^~ // (state done) // -// . ??? (??? consumed if it is a keyword) -// ^ -// (state done) +// expr . ( ... ) +// ^ +// 1. ParenExpr +// 2. CompoundMemberAccess +// +// expr -> ( ... ) +// ^~ +// 1. ParenExpr +// 2. CompoundPointerMemberAccess CARBON_PARSE_STATE_VARIANTS3(Period, Decl, Expr, Struct) +// Handles a compound member access after we parse the name expression. +// +// expr . ( expr ) +// ^ +CARBON_PARSE_STATE(CompoundMemberAccess) + +// Handles a compound pointer member access after we parse the name expression. +// +// expr -> ( expr ) +// ^ +CARBON_PARSE_STATE(CompoundPointerMemberAccess) + // Handles `->name` expressions. Identical to PeriodAsExpr except for the // leading token. // diff --git a/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon b/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon new file mode 100644 index 000000000000..6555c85f1f7c --- /dev/null +++ b/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon @@ -0,0 +1,93 @@ +// 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 + +// TODO: Decide if we want to support this syntax. See #3763. + +interface Interface { + fn F(); +} + +impl bool as Interface { + fn F(); +} + +// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+3]]:4: ERROR: `fn` introducer should be followed by a name. +// CHECK:STDERR: fn (bool as Interface).F() {} +// CHECK:STDERR: ^ +fn (bool as Interface).F() {} + +class C { + impl Self as Interface { + fn F(); + } +} + +// TODO: The error recovery here is not very good. The `(` is treated as +// starting the function parameter list. +// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+9]]:6: ERROR: Expected identifier after `.`. +// CHECK:STDERR: fn C.(Self as Interface).F() {} +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+6]]:7: ERROR: Expected binding pattern. +// CHECK:STDERR: fn C.(Self as Interface).F() {} +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+3]]:25: ERROR: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition. +// CHECK:STDERR: fn C.(Self as Interface).F() {} +// CHECK:STDERR: ^ +fn C.(Self as Interface).F() {} + +// CHECK:STDOUT: - filename: fail_out_of_line_member.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'InterfaceIntroducer', text: 'interface'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Interface'}, +// CHECK:STDOUT: {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InterfaceDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'ImplIntroducer', text: 'impl'}, +// CHECK:STDOUT: {kind: 'BoolTypeLiteral', text: 'bool'}, +// CHECK:STDOUT: {kind: 'TypeImplAs', text: 'as', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'Interface'}, +// CHECK:STDOUT: {kind: 'ImplDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ImplDefinition', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '(', has_error: yes}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: '}', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'C'}, +// CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ImplIntroducer', text: 'impl'}, +// CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, +// CHECK:STDOUT: {kind: 'TypeImplAs', text: 'as', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'Interface'}, +// CHECK:STDOUT: {kind: 'ImplDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ImplDefinition', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'C'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: '(', has_error: yes}, +// CHECK:STDOUT: {kind: 'QualifiedName', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'BindingPattern', text: 'Self', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: '}', has_error: yes, subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/member_access/compound.carbon b/toolchain/parse/testdata/member_access/compound.carbon new file mode 100644 index 000000000000..5e80ae268e2a --- /dev/null +++ b/toolchain/parse/testdata/member_access/compound.carbon @@ -0,0 +1,34 @@ +// 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 + +fn F() { + a.(b); + a->(b); +} + +// CHECK:STDOUT: - filename: compound.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/member_access/fail_keyword.carbon b/toolchain/parse/testdata/member_access/fail_keyword.carbon new file mode 100644 index 000000000000..b2ed8234557a --- /dev/null +++ b/toolchain/parse/testdata/member_access/fail_keyword.carbon @@ -0,0 +1,68 @@ +// 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 + +fn F() { + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:5: ERROR: Expected identifier after `.`. + // CHECK:STDERR: a.self; + // CHECK:STDERR: ^~~~ + a.self; + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:5: ERROR: Expected identifier after `.`. + // CHECK:STDERR: a.Self; + // CHECK:STDERR: ^~~~ + a.Self; + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:5: ERROR: Expected identifier after `.`. + // CHECK:STDERR: a.for; + // CHECK:STDERR: ^~~ + a.for; + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:6: ERROR: Expected identifier after `->`. + // CHECK:STDERR: p->self; + // CHECK:STDERR: ^~~~ + p->self; + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:6: ERROR: Expected identifier after `->`. + // CHECK:STDERR: p->Self; + // CHECK:STDERR: ^~~~ + p->Self; + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+3]]:6: ERROR: Expected identifier after `->`. + // CHECK:STDERR: p->while; + // CHECK:STDERR: ^~~~~ + p->while; +} + +// CHECK:STDOUT: - filename: fail_keyword.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'self', has_error: yes}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'for', has_error: yes}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'self', has_error: yes}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'while', has_error: yes}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 30}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/member_access/keyword.carbon b/toolchain/parse/testdata/member_access/keyword.carbon new file mode 100644 index 000000000000..a7b841c51e32 --- /dev/null +++ b/toolchain/parse/testdata/member_access/keyword.carbon @@ -0,0 +1,30 @@ +// 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 + +fn F() { + a.base; + p->base; +} + +// CHECK:STDOUT: - filename: keyword.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'BaseName', text: 'base'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, +// CHECK:STDOUT: {kind: 'BaseName', text: 'base'}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/member_access/simple.carbon b/toolchain/parse/testdata/member_access/simple.carbon new file mode 100644 index 000000000000..3dbb81c2bdf2 --- /dev/null +++ b/toolchain/parse/testdata/member_access/simple.carbon @@ -0,0 +1,47 @@ +// 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 + +fn F(a: T) { + a.x; + a->x; + a.x->y; + a->x.y; +} + +// CHECK:STDOUT: - filename: simple.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'T'}, +// CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'y'}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'y'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 29}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/struct/fail_dot_paren.carbon b/toolchain/parse/testdata/struct/fail_dot_paren.carbon new file mode 100644 index 000000000000..d78c0e8d8348 --- /dev/null +++ b/toolchain/parse/testdata/struct/fail_dot_paren.carbon @@ -0,0 +1,26 @@ +// 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 + +// CHECK:STDERR: fail_dot_paren.carbon:[[@LINE+3]]:10: ERROR: Expected identifier after `.`. +// CHECK:STDERR: var x: {.(a) = 1}; +// CHECK:STDERR: ^ +var x: {.(a) = 1}; + +// CHECK:STDOUT: - filename: fail_dot_paren.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: '(', has_error: yes}, +// CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '1'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 397bd1b3bd6d..a7d04cf73341 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -620,7 +620,8 @@ using ExprOpenParen = LeafNode; // A parenthesized expression: `(a)`. struct ParenExpr { - static constexpr auto Kind = NodeKind::ParenExpr.Define(NodeCategory::Expr); + static constexpr auto Kind = + NodeKind::ParenExpr.Define(NodeCategory::Expr | NodeCategory::MemberExpr); ExprOpenParenId left_paren; AnyExprId expr; @@ -656,22 +657,22 @@ struct CallExpr { CommaSeparatedList arguments; }; -// A simple member access expression: `a.b`. +// A member access expression: `a.b` or `a.(b)`. struct MemberAccessExpr { static constexpr auto Kind = NodeKind::MemberAccessExpr.Define(NodeCategory::Expr); AnyExprId lhs; - AnyMemberNameId rhs; + AnyMemberNameOrMemberExprId rhs; }; -// A simple indirect member access expression: `a->b`. +// An indirect member access expression: `a->b` or `a->(b)`. struct PointerMemberAccessExpr { static constexpr auto Kind = NodeKind::PointerMemberAccessExpr.Define(NodeCategory::Expr); AnyExprId lhs; - AnyMemberNameId rhs; + AnyMemberNameOrMemberExprId rhs; }; // A prefix operator expression.