diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 72cf582489cb..7faedc97da86 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -859,6 +859,62 @@ auto ConvertForExplicitAs(Context& context, Parse::Node as_node, {.kind = ConversionTarget::ExplicitAs, .type_id = type_id}); } +CARBON_DIAGNOSTIC(InCallToFunction, Note, "Calling function declared here."); + +// Convert the object argument in a method call to match the `self` parameter. +static auto ConvertSelf(Context& context, Parse::Node call_parse_node, + Parse::Node callee_parse_node, + SemIR::SelfParameter self_param, SemIR::NodeId self_id) + -> SemIR::NodeId { + if (!self_id.is_valid()) { + CARBON_DIAGNOSTIC(MissingObjectInMethodCall, Error, + "Missing object argument in method call."); + context.emitter() + .Build(call_parse_node, MissingObjectInMethodCall) + .Note(callee_parse_node, InCallToFunction) + .Emit(); + return SemIR::NodeId::BuiltinError; + } + + DiagnosticAnnotationScope annotate_diagnostics( + &context.emitter(), [&](auto& builder) { + CARBON_DIAGNOSTIC( + InCallToFunctionSelf, Note, + "Initializing `{0}` parameter of method declared here.", + llvm::StringLiteral); + builder.Note(self_param.parse_node, InCallToFunctionSelf, + self_param.is_addr_self.index + ? llvm::StringLiteral("addr self") + : llvm::StringLiteral("self")); + }); + + // For `addr self`, take the address of the object argument. + auto self_or_addr_id = self_id; + if (self_param.is_addr_self.index) { + self_or_addr_id = + ConvertToValueOrReferenceExpression(context, self_or_addr_id); + auto self = context.nodes().Get(self_or_addr_id); + switch (SemIR::GetExpressionCategory(context.sem_ir(), self_id)) { + case SemIR::ExpressionCategory::Error: + case SemIR::ExpressionCategory::DurableReference: + case SemIR::ExpressionCategory::EphemeralReference: + break; + default: + CARBON_DIAGNOSTIC(AddrSelfIsNonReference, Error, + "`addr self` method cannot be invoked on a value."); + context.emitter().Emit(call_parse_node, AddrSelfIsNonReference); + return SemIR::NodeId::BuiltinError; + } + self_or_addr_id = context.AddNode(SemIR::AddressOf{ + self.parse_node(), + context.GetPointerType(self.parse_node(), self.type_id()), + self_or_addr_id}); + } + + return ConvertToValueOfType(context, call_parse_node, self_or_addr_id, + self_param.type_id); +} + auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, SemIR::NodeId self_id, llvm::ArrayRef arg_refs, @@ -870,8 +926,6 @@ auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, context.sem_ir().node_blocks().Get(implicit_param_refs_id); auto param_refs = context.sem_ir().node_blocks().Get(param_refs_id); - CARBON_DIAGNOSTIC(InCallToFunction, Note, "Calling function declared here."); - // If sizes mismatch, fail early. if (arg_refs.size() != param_refs.size()) { CARBON_DIAGNOSTIC(CallArgCountMismatch, Error, @@ -895,27 +949,8 @@ auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, for (auto implicit_param_id : implicit_param_refs) { auto param = context.nodes().Get(implicit_param_id); if (auto self_param = param.TryAs()) { - if (!self_id.is_valid()) { - CARBON_DIAGNOSTIC(MissingObjectInMethodCall, Error, - "Missing object argument in method call."); - context.emitter() - .Build(call_parse_node, MissingObjectInMethodCall) - .Note(callee_parse_node, InCallToFunction) - .Emit(); - return SemIR::NodeBlockId::Invalid; - } - - DiagnosticAnnotationScope annotate_diagnostics( - &context.emitter(), [&](auto& builder) { - CARBON_DIAGNOSTIC( - InCallToFunctionSelf, Note, - "Initializing self parameter of method declared here."); - builder.Note(self_param->parse_node, InCallToFunctionSelf); - }); - - // TODO: Handle `addr self`. - auto converted_self_id = ConvertToValueOfType( - context, call_parse_node, self_id, self_param->type_id); + auto converted_self_id = ConvertSelf( + context, call_parse_node, callee_parse_node, *self_param, self_id); if (converted_self_id == SemIR::NodeId::BuiltinError) { return SemIR::NodeBlockId::Invalid; } diff --git a/toolchain/check/testdata/class/fail_addr_self.carbon b/toolchain/check/testdata/class/fail_addr_self.carbon index c8cf8e795bd9..0936d3093c58 100644 --- a/toolchain/check/testdata/class/fail_addr_self.carbon +++ b/toolchain/check/testdata/class/fail_addr_self.carbon @@ -9,18 +9,33 @@ class Class { fn G[addr self: Class](); } -fn F(c: Class) { - // TODO: This is the wrong reason to reject this. We should complain that `c` is not an lvalue. - // CHECK:STDERR: fail_addr_self.carbon:[[@LINE+6]]:6: ERROR: Cannot implicitly convert from `Class` to `Class*`. +fn F(c: Class, p: Class*) { + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE+6]]:6: ERROR: `addr self` method cannot be invoked on a value. // CHECK:STDERR: c.F(); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_addr_self.carbon:[[@LINE-9]]:13: Initializing self parameter of method declared here. + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE-8]]:13: Initializing `addr self` parameter of method declared here. // CHECK:STDERR: fn F[addr self: Class*](); // CHECK:STDERR: ^ c.F(); - // TODO: This is invalid and should be rejected. + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE+6]]:6: ERROR: `addr self` method cannot be invoked on a value. + // CHECK:STDERR: c.G(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE-15]]:13: Initializing `addr self` parameter of method declared here. + // CHECK:STDERR: fn G[addr self: Class](); + // CHECK:STDERR: ^ c.G(); + + // This call is OK. + (*p).F(); + + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE+6]]:9: ERROR: Cannot implicitly convert from `Class*` to `Class`. + // CHECK:STDERR: (*p).G(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE-26]]:13: Initializing `addr self` parameter of method declared here. + // CHECK:STDERR: fn G[addr self: Class](); + // CHECK:STDERR: ^ + (*p).G(); } // CHECK:STDOUT: file "fail_addr_self.carbon" { @@ -43,15 +58,25 @@ fn F(c: Class) { // CHECK:STDOUT: // CHECK:STDOUT: fn @G[%self.addr: Class](); // CHECK:STDOUT: -// CHECK:STDOUT: fn @F.2(%c: Class) { +// CHECK:STDOUT: fn @F.2(%c: Class, %p: Class*) { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc10: type = tuple_type () // CHECK:STDOUT: %.loc7: type = ptr_type {} -// CHECK:STDOUT: %c.ref.loc20: Class = name_reference "c", %c -// CHECK:STDOUT: %.loc20_4: = bound_method %c.ref.loc20, @Class.%F -// CHECK:STDOUT: %.loc20_6: init () = call %.loc20_4() -// CHECK:STDOUT: %c.ref.loc23: Class = name_reference "c", %c -// CHECK:STDOUT: %.loc23_4: = bound_method %c.ref.loc23, @Class.%G -// CHECK:STDOUT: %.loc23_6: init () = call %.loc23_4(%c.ref.loc23) +// CHECK:STDOUT: %c.ref.loc19: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc19_4: = bound_method %c.ref.loc19, @Class.%F +// CHECK:STDOUT: %.loc19_6: init () = call %.loc19_4() +// CHECK:STDOUT: %c.ref.loc27: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc27_4: = bound_method %c.ref.loc27, @Class.%G +// CHECK:STDOUT: %.loc27_6: init () = call %.loc27_4() +// CHECK:STDOUT: %p.ref.loc30: Class* = name_reference "p", %p +// CHECK:STDOUT: %.loc30_4.1: ref Class = dereference %p.ref.loc30 +// CHECK:STDOUT: %.loc30_7: = bound_method %.loc30_4.1, @Class.%F +// CHECK:STDOUT: %.loc30_4.2: Class* = address_of %.loc30_4.1 +// CHECK:STDOUT: %.loc30_9: init () = call %.loc30_7(%.loc30_4.2) +// CHECK:STDOUT: %p.ref.loc38: Class* = name_reference "p", %p +// CHECK:STDOUT: %.loc38_4.1: ref Class = dereference %p.ref.loc38 +// CHECK:STDOUT: %.loc38_7: = bound_method %.loc38_4.1, @Class.%G +// CHECK:STDOUT: %.loc38_4.2: Class* = address_of %.loc38_4.1 +// CHECK:STDOUT: %.loc38_9: init () = call %.loc38_7() // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_self.carbon b/toolchain/check/testdata/class/fail_self.carbon index 19e343b1cbb1..231049b1b5e2 100644 --- a/toolchain/check/testdata/class/fail_self.carbon +++ b/toolchain/check/testdata/class/fail_self.carbon @@ -44,7 +44,7 @@ fn CallWrongSelf(ws: WrongSelf) { // CHECK:STDERR: fail_self.carbon:[[@LINE+6]]:7: ERROR: Cannot implicitly convert from `WrongSelf` to `Class`. // CHECK:STDERR: ws.F(); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_self.carbon:[[@LINE-7]]:8: Initializing self parameter of method declared here. + // CHECK:STDERR: fail_self.carbon:[[@LINE-7]]:8: Initializing `self` parameter of method declared here. // CHECK:STDERR: fn F[self: Class](); // CHECK:STDERR: ^ ws.F(); diff --git a/toolchain/check/testdata/class/method.carbon b/toolchain/check/testdata/class/method.carbon index 51353d776c7d..fc76b538e0eb 100644 --- a/toolchain/check/testdata/class/method.carbon +++ b/toolchain/check/testdata/class/method.carbon @@ -6,6 +6,7 @@ class Class { fn F[self: Class]() -> i32; + fn G[addr self: Class*]() -> i32; var k: i32; } @@ -20,22 +21,53 @@ fn Call(c: Class) -> i32 { return c.F(); } +fn CallWithAddr() -> i32 { + var c: Class; + return c.G(); +} + +fn CallFThroughPointer(p: Class*) -> i32 { + return (*p).F(); +} + +fn CallGThroughPointer(p: Class*) -> i32 { + return (*p).G(); +} + +fn Make() -> Class; + +fn CallFOnInitializingExpression() -> i32 { + return Make().F(); +} + +fn CallGOnInitializingExpression() -> i32 { + return Make().G(); +} + // CHECK:STDOUT: file "method.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class -// CHECK:STDOUT: %.loc11: type = struct_type {.k: i32} +// CHECK:STDOUT: %.loc12: type = struct_type {.k: i32} // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: %Call: = fn_decl @Call +// CHECK:STDOUT: %CallWithAddr: = fn_decl @CallWithAddr +// CHECK:STDOUT: %CallFThroughPointer: = fn_decl @CallFThroughPointer +// CHECK:STDOUT: %CallGThroughPointer: = fn_decl @CallGThroughPointer +// CHECK:STDOUT: %Make: = fn_decl @Make +// CHECK:STDOUT: %CallFOnInitializingExpression: = fn_decl @CallFOnInitializingExpression +// CHECK:STDOUT: %CallGOnInitializingExpression: = fn_decl @CallGOnInitializingExpression // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { // CHECK:STDOUT: %F: = fn_decl @F -// CHECK:STDOUT: %.loc10_8.1: type = unbound_field_type Class, i32 -// CHECK:STDOUT: %.loc10_8.2: = field "k", member0 -// CHECK:STDOUT: %k: = bind_name "k", %.loc10_8.2 +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %.loc11_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc11_8.2: = field "k", member0 +// CHECK:STDOUT: %k: = bind_name "k", %.loc11_8.2 // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G // CHECK:STDOUT: .k = %k // CHECK:STDOUT: } // CHECK:STDOUT: @@ -43,18 +75,93 @@ fn Call(c: Class) -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc7: type = ptr_type {.k: i32} // CHECK:STDOUT: %self.ref: Class = name_reference "self", %self -// CHECK:STDOUT: %.loc14_14.1: ref i32 = class_field_access %self.ref, member0 -// CHECK:STDOUT: %.loc14_14.2: i32 = bind_value %.loc14_14.1 -// CHECK:STDOUT: return %.loc14_14.2 +// CHECK:STDOUT: %.loc15_14.1: ref i32 = class_field_access %self.ref, member0 +// CHECK:STDOUT: %.loc15_14.2: i32 = bind_value %.loc15_14.1 +// CHECK:STDOUT: return %.loc15_14.2 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @G[%self.addr: Class*]() -> i32; +// CHECK:STDOUT: // CHECK:STDOUT: fn @Call(%c: Class) -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %c.ref: Class = name_reference "c", %c -// CHECK:STDOUT: %.loc20_11: = bound_method %c.ref, @Class.%F -// CHECK:STDOUT: %.loc20_13.1: init i32 = call %.loc20_11(%c.ref) -// CHECK:STDOUT: %.loc20_13.2: ref i32 = temporary_storage -// CHECK:STDOUT: %.loc20_13.3: ref i32 = temporary %.loc20_13.2, %.loc20_13.1 -// CHECK:STDOUT: %.loc20_13.4: i32 = bind_value %.loc20_13.3 -// CHECK:STDOUT: return %.loc20_13.4 +// CHECK:STDOUT: %.loc21_11: = bound_method %c.ref, @Class.%F +// CHECK:STDOUT: %.loc21_13.1: init i32 = call %.loc21_11(%c.ref) +// CHECK:STDOUT: %.loc21_13.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc21_13.3: ref i32 = temporary %.loc21_13.2, %.loc21_13.1 +// CHECK:STDOUT: %.loc21_13.4: i32 = bind_value %.loc21_13.3 +// CHECK:STDOUT: return %.loc21_13.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallWithAddr() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class +// 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: %.loc26_11: = bound_method %c.ref, @Class.%G +// CHECK:STDOUT: %.loc26_10: Class* = address_of %c.ref +// CHECK:STDOUT: %.loc26_13.1: init i32 = call %.loc26_11(%.loc26_10) +// CHECK:STDOUT: %.loc26_13.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc26_13.3: ref i32 = temporary %.loc26_13.2, %.loc26_13.1 +// CHECK:STDOUT: %.loc26_13.4: i32 = bind_value %.loc26_13.3 +// CHECK:STDOUT: return %.loc26_13.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallFThroughPointer(%p: Class*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p +// CHECK:STDOUT: %.loc30_11.1: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc30_14: = bound_method %.loc30_11.1, @Class.%F +// CHECK:STDOUT: %.loc30_11.2: Class = bind_value %.loc30_11.1 +// CHECK:STDOUT: %.loc30_16.1: init i32 = call %.loc30_14(%.loc30_11.2) +// CHECK:STDOUT: %.loc30_16.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc30_16.3: ref i32 = temporary %.loc30_16.2, %.loc30_16.1 +// CHECK:STDOUT: %.loc30_16.4: i32 = bind_value %.loc30_16.3 +// CHECK:STDOUT: return %.loc30_16.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallGThroughPointer(%p: Class*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p +// CHECK:STDOUT: %.loc34_11.1: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc34_14: = bound_method %.loc34_11.1, @Class.%G +// CHECK:STDOUT: %.loc34_11.2: Class* = address_of %.loc34_11.1 +// CHECK:STDOUT: %.loc34_16.1: init i32 = call %.loc34_14(%.loc34_11.2) +// CHECK:STDOUT: %.loc34_16.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc34_16.3: ref i32 = temporary %.loc34_16.2, %.loc34_16.1 +// CHECK:STDOUT: %.loc34_16.4: i32 = bind_value %.loc34_16.3 +// CHECK:STDOUT: return %.loc34_16.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Make() -> %return: Class; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallFOnInitializingExpression() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Make.ref: = name_reference "Make", file.%Make +// CHECK:STDOUT: %.loc40_14.1: ref Class = temporary_storage +// CHECK:STDOUT: %.loc40_14.2: init Class = call %Make.ref() to %.loc40_14.1 +// CHECK:STDOUT: %.loc40_14.3: ref Class = temporary %.loc40_14.1, %.loc40_14.2 +// CHECK:STDOUT: %.loc40_16: = bound_method %.loc40_14.3, @Class.%F +// CHECK:STDOUT: %.loc40_14.4: Class = bind_value %.loc40_14.3 +// CHECK:STDOUT: %.loc40_18.1: init i32 = call %.loc40_16(%.loc40_14.4) +// CHECK:STDOUT: %.loc40_18.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc40_18.3: ref i32 = temporary %.loc40_18.2, %.loc40_18.1 +// CHECK:STDOUT: %.loc40_18.4: i32 = bind_value %.loc40_18.3 +// CHECK:STDOUT: return %.loc40_18.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallGOnInitializingExpression() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Make.ref: = name_reference "Make", file.%Make +// CHECK:STDOUT: %.loc44_14.1: ref Class = temporary_storage +// CHECK:STDOUT: %.loc44_14.2: init Class = call %Make.ref() to %.loc44_14.1 +// CHECK:STDOUT: %.loc44_14.3: ref Class = temporary %.loc44_14.1, %.loc44_14.2 +// CHECK:STDOUT: %.loc44_16: = bound_method %.loc44_14.3, @Class.%G +// CHECK:STDOUT: %.loc44_14.4: Class* = address_of %.loc44_14.3 +// CHECK:STDOUT: %.loc44_18.1: init i32 = call %.loc44_16(%.loc44_14.4) +// CHECK:STDOUT: %.loc44_18.2: ref i32 = temporary_storage +// CHECK:STDOUT: %.loc44_18.3: ref i32 = temporary %.loc44_18.2, %.loc44_18.1 +// CHECK:STDOUT: %.loc44_18.4: i32 = bind_value %.loc44_18.3 +// CHECK:STDOUT: return %.loc44_18.4 // CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index fb195dd2834e..6aa1305decdb 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -110,6 +110,7 @@ CARBON_DIAGNOSTIC_KIND(ParametersRequiredByIntroducer) CARBON_DIAGNOSTIC_KIND(SemanticsTodo) // Function call checking. +CARBON_DIAGNOSTIC_KIND(AddrSelfIsNonReference) CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch) CARBON_DIAGNOSTIC_KIND(CallToNonCallable) CARBON_DIAGNOSTIC_KIND(InCallToFunction) diff --git a/toolchain/lower/testdata/class/method.carbon b/toolchain/lower/testdata/class/method.carbon index cee47b9d5ce9..f84fcbbc3b11 100644 --- a/toolchain/lower/testdata/class/method.carbon +++ b/toolchain/lower/testdata/class/method.carbon @@ -13,8 +13,7 @@ class C { fn F(p: C*) { let n: i32 = (*p).Get(); - // TODO: Support `addr self`. - // (*p).Set(n); + (*p).Set(n); } // CHECK:STDOUT: ; ModuleID = 'method.carbon' @@ -29,5 +28,6 @@ fn F(p: C*) { // CHECK:STDOUT: %temp = alloca i32, align 4 // CHECK:STDOUT: store i32 %Get, ptr %temp, align 4 // CHECK:STDOUT: %1 = load i32, ptr %temp, align 4 +// CHECK:STDOUT: call void @Set(ptr %p, i32 %1) // CHECK:STDOUT: ret void // CHECK:STDOUT: }