From 57f3c553b86ec678a1dda067b8aba3cc3e29ed60 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 Oct 2023 13:41:13 -0700 Subject: [PATCH] Support for type-checking and lowering method calls. (#3343) Adds a `BoundMethod` SemIR node to represent an `x.F` bound method, with a new builtin type `BoundMethodType`. Reorganized conversion of call expression arguments to also check and convert a `self` parameter in the implicit parameters list. In passing, improved diagnostics and error recovery for bad call expressions. We now build a `call` node with the appropriate type and value category, but with invalid arguments, if the argument conversion failed, and diagnose calls to non-callable expressions. `addr self` methods don't work properly yet; the `addr` is ignored for now. --------- Co-authored-by: Jon Ross-Perkins --- toolchain/check/context.cpp | 3 + toolchain/check/context.h | 4 + toolchain/check/convert.cpp | 99 ++++++++++++------- toolchain/check/convert.h | 13 ++- toolchain/check/handle_call_expression.cpp | 82 ++++++++------- toolchain/check/handle_name.cpp | 27 +++++ .../testdata/basics/builtin_nodes.carbon | 1 + .../testdata/class/fail_addr_self.carbon | 57 +++++++++++ .../testdata/class/fail_incomplete.carbon | 45 +++++---- .../check/testdata/class/fail_method.carbon | 79 +++++++++------ .../testdata/class/fail_redefinition.carbon | 4 +- .../check/testdata/class/fail_reorder.carbon | 13 +-- .../check/testdata/class/fail_scope.carbon | 7 +- .../check/testdata/class/fail_self.carbon | 40 +++++++- toolchain/check/testdata/class/method.carbon | 60 +++++++++++ .../function/call/fail_not_callable.carbon | 4 +- .../function/call/fail_param_count.carbon | 10 +- .../function/call/fail_param_type.carbon | 3 +- toolchain/diagnostics/diagnostic_kind.def | 11 ++- toolchain/lower/file_context.cpp | 1 + toolchain/lower/function_context.cpp | 12 +++ toolchain/lower/function_context.h | 3 + toolchain/lower/handle.cpp | 17 ++-- toolchain/lower/testdata/class/method.carbon | 33 +++++++ toolchain/sem_ir/builtin_kind.def | 3 + toolchain/sem_ir/file.cpp | 3 + toolchain/sem_ir/formatter.cpp | 5 + toolchain/sem_ir/node_kind.def | 1 + toolchain/sem_ir/typed_nodes.h | 18 ++++ 29 files changed, 493 insertions(+), 165 deletions(-) create mode 100644 toolchain/check/testdata/class/fail_addr_self.carbon create mode 100644 toolchain/check/testdata/class/method.carbon create mode 100644 toolchain/lower/testdata/class/method.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index d0bc6afc9982..06f15753dd4d 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -185,6 +185,7 @@ auto Context::GetConstantValue(SemIR::NodeId node_id) -> SemIR::NodeId { break; case SemIR::Field::Kind: + case SemIR::FunctionDeclaration::Kind: return node_id; default: @@ -538,6 +539,7 @@ class TypeCompleter { case SemIR::BuiltinKind::FloatingPointType: case SemIR::BuiltinKind::NamespaceType: case SemIR::BuiltinKind::FunctionType: + case SemIR::BuiltinKind::BoundMethodType: return MakeCopyRepresentation(type_id); case SemIR::BuiltinKind::StringType: @@ -657,6 +659,7 @@ class TypeCompleter { case SemIR::BindValue::Kind: case SemIR::BlockArg::Kind: case SemIR::BoolLiteral::Kind: + case SemIR::BoundMethod::Kind: case SemIR::Branch::Kind: case SemIR::BranchIf::Kind: case SemIR::BranchWithArg::Kind: diff --git a/toolchain/check/context.h b/toolchain/check/context.h index eb837751b195..46bc905a0a55 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -224,6 +224,10 @@ class Context { auto node_block_stack() -> NodeBlockStack& { return node_block_stack_; } + auto params_or_args_stack() -> NodeBlockStack& { + return params_or_args_stack_; + } + auto args_type_info_stack() -> NodeBlockStack& { return args_type_info_stack_; } diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 689a2607782a..1985e7b3a132 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -773,27 +773,17 @@ auto ConvertToBoolValue(Context& context, Parse::Node parse_node, } auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, - SemIR::NodeBlockId arg_refs_id, - Parse::Node param_parse_node, - SemIR::NodeBlockId param_refs_id, bool has_return_slot) - -> bool { - // If both arguments and parameters are empty, return quickly. Otherwise, - // we'll fetch both so that errors are consistent. - if (arg_refs_id == SemIR::NodeBlockId::Empty && - param_refs_id == SemIR::NodeBlockId::Empty) { - return true; - } - - auto arg_refs = context.sem_ir().node_blocks().Get(arg_refs_id); + SemIR::NodeId self_id, + llvm::ArrayRef arg_refs, + SemIR::NodeId return_storage_id, + Parse::Node callee_parse_node, + SemIR::NodeBlockId implicit_param_refs_id, + SemIR::NodeBlockId param_refs_id) -> SemIR::NodeBlockId { + auto implicit_param_refs = + context.sem_ir().node_blocks().Get(implicit_param_refs_id); auto param_refs = context.sem_ir().node_blocks().Get(param_refs_id); - if (has_return_slot) { - // There's no entry in the parameter block for the return slot, so ignore - // the corresponding entry in the argument block. - // TODO: Consider adding the return slot to the parameter list. - CARBON_CHECK(!arg_refs.empty()) << "missing return slot"; - arg_refs = arg_refs.drop_back(); - } + CARBON_DIAGNOSTIC(InCallToFunction, Note, "Calling function declared here."); // If sizes mismatch, fail early. if (arg_refs.size() != param_refs.size()) { @@ -801,18 +791,53 @@ auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, "{0} argument(s) passed to function expecting " "{1} argument(s).", int, int); - CARBON_DIAGNOSTIC(InCallToFunction, Note, - "Calling function declared here."); context.emitter() .Build(call_parse_node, CallArgCountMismatch, arg_refs.size(), param_refs.size()) - .Note(param_parse_node, InCallToFunction) + .Note(callee_parse_node, InCallToFunction) .Emit(); - return false; + return SemIR::NodeBlockId::Invalid; } - if (param_refs.empty()) { - return true; + // Start building a block to hold the converted arguments. + llvm::SmallVector args; + args.reserve(implicit_param_refs.size() + param_refs.size() + + return_storage_id.is_valid()); + + // Check implicit parameters. + 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); + if (converted_self_id == SemIR::NodeId::BuiltinError) { + return SemIR::NodeBlockId::Invalid; + } + args.push_back(converted_self_id); + } else { + // TODO: Form argument values for implicit parameters. + context.TODO(call_parse_node, "Call with implicit parameters"); + return SemIR::NodeBlockId::Invalid; + } } int diag_param_index; @@ -821,26 +846,32 @@ auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, CARBON_DIAGNOSTIC( InCallToFunctionParam, Note, "Initializing parameter {0} of function declared here.", int); - builder.Note(param_parse_node, InCallToFunctionParam, + builder.Note(callee_parse_node, InCallToFunctionParam, diag_param_index + 1); }); // Check type conversions per-element. - for (auto [i, value_id, param_ref] : llvm::enumerate(arg_refs, param_refs)) { + for (auto [i, arg_id, param_id] : llvm::enumerate(arg_refs, param_refs)) { diag_param_index = i; - auto as_type_id = context.sem_ir().nodes().Get(param_ref).type_id(); + auto param_type_id = context.sem_ir().nodes().Get(param_id).type_id(); // TODO: Convert to the proper expression category. For now, we assume // parameters are all `let` bindings. - value_id = - ConvertToValueOfType(context, call_parse_node, value_id, as_type_id); - if (value_id == SemIR::NodeId::BuiltinError) { - return false; + auto converted_arg_id = + ConvertToValueOfType(context, call_parse_node, arg_id, param_type_id); + if (converted_arg_id == SemIR::NodeId::BuiltinError) { + return SemIR::NodeBlockId::Invalid; } - arg_refs[i] = value_id; + + args.push_back(converted_arg_id); } - return true; + // Track the return storage, if present. + if (return_storage_id.is_valid()) { + args.push_back(return_storage_id); + } + + return context.node_blocks().Add(args); } auto ExpressionAsType(Context& context, Parse::Node parse_node, diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 7991ea3585c4..d4e3d166524d 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -77,12 +77,15 @@ auto ConvertToBoolValue(Context& context, Parse::Node parse_node, SemIR::NodeId value_id) -> SemIR::NodeId; // Implicitly converts a set of arguments to match the parameter types in a -// function call. +// function call. Returns a block containing the converted implicit and explicit +// argument values. auto ConvertCallArgs(Context& context, Parse::Node call_parse_node, - SemIR::NodeBlockId arg_refs_id, - Parse::Node param_parse_node, - SemIR::NodeBlockId param_refs_id, bool has_return_slot) - -> bool; + SemIR::NodeId self_id, + llvm::ArrayRef arg_refs, + SemIR::NodeId return_storage_id, + Parse::Node callee_parse_node, + SemIR::NodeBlockId implicit_param_refs_id, + SemIR::NodeBlockId param_refs_id) -> SemIR::NodeBlockId; // Converts an expression for use as a type. auto ExpressionAsType(Context& context, Parse::Node parse_node, diff --git a/toolchain/check/handle_call_expression.cpp b/toolchain/check/handle_call_expression.cpp index c96841cefdbe..1692975ee38c 100644 --- a/toolchain/check/handle_call_expression.cpp +++ b/toolchain/check/handle_call_expression.cpp @@ -2,6 +2,7 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include "llvm/ADT/ScopeExit.h" #include "toolchain/check/context.h" #include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" @@ -9,26 +10,50 @@ namespace Carbon::Check { auto HandleCallExpression(Context& context, Parse::Node parse_node) -> bool { - // Process the final explicit call argument, but leave the arguments block on - // the stack until we add the return slot argument. + // Process the final explicit call argument now, but leave the arguments + // block on the stack until the end of this function. context.ParamOrArgEndNoPop(Parse::NodeKind::CallExpressionStart); + auto discard_args_block = llvm::make_scope_exit( + [&] { context.params_or_args_stack().PopAndDiscard(); }); - // TODO: Convert to call expression. auto [call_expr_parse_node, callee_id] = context.node_stack() .PopWithParseNode(); - auto callee_node = - context.nodes().Get(context.FollowNameReferences(callee_id)); - auto function_name = callee_node.TryAs(); - if (!function_name) { - // TODO: Work on error. - context.TODO(parse_node, "Not a callable name"); + + auto diagnose_not_callable = [&, call_expr_parse_node = call_expr_parse_node, + callee_id = callee_id] { + auto callee_type_id = context.nodes().Get(callee_id).type_id(); + if (callee_type_id != SemIR::TypeId::Error) { + CARBON_DIAGNOSTIC(CallToNonCallable, Error, + "Value of type `{0}` is not callable.", std::string); + context.emitter().Emit( + call_expr_parse_node, CallToNonCallable, + context.sem_ir().StringifyType(callee_type_id, true)); + } context.node_stack().Push(parse_node, SemIR::NodeId::BuiltinError); - context.ParamOrArgPop(); return true; + }; + + // For a method call, pick out the `self` value. + auto function_callee_id = callee_id; + SemIR::NodeId self_id = SemIR::NodeId::Invalid; + if (auto bound_method = + context.nodes().Get(callee_id).TryAs()) { + self_id = bound_method->object_id; + function_callee_id = bound_method->function_id; } - auto function_id = function_name->function_id; + // Identify the function we're calling. + auto function_decl_id = context.GetConstantValue(function_callee_id); + if (!function_decl_id.is_valid()) { + return diagnose_not_callable(); + } + auto function_decl = + context.nodes().Get(function_decl_id).TryAs(); + if (!function_decl) { + return diagnose_not_callable(); + } + auto function_id = function_decl->function_id; const auto& callable = context.functions().Get(function_id); // For functions with an implicit return type, the return type is the empty @@ -38,40 +63,23 @@ auto HandleCallExpression(Context& context, Parse::Node parse_node) -> bool { type_id = context.CanonicalizeTupleType(call_expr_parse_node, {}); } - // If there is a return slot, add a corresponding argument. + // If there is a return slot, build storage for the result. + SemIR::NodeId return_storage_id = SemIR::NodeId::Invalid; if (callable.return_slot_id.is_valid()) { // Tentatively put storage for a temporary in the function's return slot. // This will be replaced if necessary when we perform initialization. - auto temp_id = context.AddNode( + return_storage_id = context.AddNode( SemIR::TemporaryStorage{call_expr_parse_node, callable.return_type_id}); - context.ParamOrArgSave(temp_id); - } - - for (auto implicit_param_id : - context.node_blocks().Get(callable.implicit_param_refs_id)) { - auto param = context.nodes().Get(implicit_param_id); - if (auto self_param = param.TryAs()) { - // TODO: Handle `self` parameter. - } - - // TODO: Form argument values for implicit parameters. - context.TODO(parse_node, "Call with implicit parameters"); - context.node_stack().Push(parse_node, SemIR::NodeId::BuiltinError); - context.ParamOrArgPop(); - return true; } // Convert the arguments to match the parameters. - auto refs_id = context.ParamOrArgPop(); - if (!ConvertCallArgs(context, call_expr_parse_node, refs_id, - callee_node.parse_node(), callable.param_refs_id, - callable.return_slot_id.is_valid())) { - context.node_stack().Push(parse_node, SemIR::NodeId::BuiltinError); - return true; - } - + auto converted_args_id = + ConvertCallArgs(context, call_expr_parse_node, self_id, + context.params_or_args_stack().PeekCurrentBlockContents(), + return_storage_id, function_decl->parse_node, + callable.implicit_param_refs_id, callable.param_refs_id); auto call_node_id = context.AddNode( - SemIR::Call{call_expr_parse_node, type_id, callee_id, refs_id}); + SemIR::Call{call_expr_parse_node, type_id, callee_id, converted_args_id}); context.node_stack().Push(parse_node, call_node_id); return true; diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index ddc80a479bf8..137a68a4a010 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -113,6 +113,33 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) base_id, field->index}); return true; } + if (member_type_id == + context.GetBuiltinType(SemIR::BuiltinKind::FunctionType)) { + // Find the named function and check whether it's an instance method. + auto function_name_id = context.GetConstantValue(member_id); + CARBON_CHECK(function_name_id.is_valid()) + << "Non-constant value " << context.nodes().Get(member_id) + << " of function type"; + auto function_decl = context.nodes() + .Get(function_name_id) + .TryAs(); + CARBON_CHECK(function_decl) + << "Unexpected value " << context.nodes().Get(function_name_id) + << " of function type"; + auto& function = context.functions().Get(function_decl->function_id); + for (auto param_id : + context.node_blocks().Get(function.implicit_param_refs_id)) { + if (context.nodes().Get(param_id).Is()) { + context.AddNodeAndPush( + parse_node, + SemIR::BoundMethod{ + parse_node, + context.GetBuiltinType(SemIR::BuiltinKind::BoundMethodType), + base_id, member_id}); + return true; + } + } + } // For a non-instance member, the result is that member. // TODO: Track that this was named within `base_id`. diff --git a/toolchain/check/testdata/basics/builtin_nodes.carbon b/toolchain/check/testdata/basics/builtin_nodes.carbon index 2e12bacb06bc..d50c1eb4af2e 100644 --- a/toolchain/check/testdata/basics/builtin_nodes.carbon +++ b/toolchain/check/testdata/basics/builtin_nodes.carbon @@ -22,6 +22,7 @@ // CHECK:STDOUT: nodeFloatingPointType: {kind: CrossReference, arg0: ir0, arg1: nodeFloatingPointType, type: typeTypeType} // CHECK:STDOUT: nodeStringType: {kind: CrossReference, arg0: ir0, arg1: nodeStringType, type: typeTypeType} // CHECK:STDOUT: nodeFunctionType: {kind: CrossReference, arg0: ir0, arg1: nodeFunctionType, type: typeTypeType} +// CHECK:STDOUT: nodeBoundMethodType: {kind: CrossReference, arg0: ir0, arg1: nodeBoundMethodType, type: typeTypeType} // CHECK:STDOUT: nodeNamespaceType: {kind: CrossReference, arg0: ir0, arg1: nodeNamespaceType, type: typeTypeType} // CHECK:STDOUT: node_blocks: // CHECK:STDOUT: block0: {} diff --git a/toolchain/check/testdata/class/fail_addr_self.carbon b/toolchain/check/testdata/class/fail_addr_self.carbon new file mode 100644 index 000000000000..c8cf8e795bd9 --- /dev/null +++ b/toolchain/check/testdata/class/fail_addr_self.carbon @@ -0,0 +1,57 @@ +// 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[addr self: 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*`. + // CHECK:STDERR: c.F(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_addr_self.carbon:[[@LINE-9]]:13: Initializing 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. + c.G(); +} + +// CHECK:STDOUT: file "fail_addr_self.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {} +// CHECK:STDOUT: %F: = fn_decl @F.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F.1 +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1[%self.addr: Class*](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G[%self.addr: Class](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2(%c: 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: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_incomplete.carbon b/toolchain/check/testdata/class/fail_incomplete.carbon index 6a45264922c6..ced265c8841e 100644 --- a/toolchain/check/testdata/class/fail_incomplete.carbon +++ b/toolchain/check/testdata/class/fail_incomplete.carbon @@ -15,22 +15,19 @@ class Class; fn Class.Function() {} fn CallClassFunction() { - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:3: ERROR: Member access into incomplete class `Class`. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:3: ERROR: Member access into incomplete class `Class`. // CHECK:STDERR: Class.Function(); // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-14]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+3]]:18: ERROR: Semantics TODO: `Not a callable name`. - // CHECK:STDERR: Class.Function(); - // CHECK:STDERR: ^ Class.Function(); } // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:17: ERROR: Variable has incomplete type `Class`. // CHECK:STDERR: var global_var: Class; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-26]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-23]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ var global_var: Class; @@ -38,7 +35,7 @@ var global_var: Class; // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:24: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn ConvertFromStruct() -> Class { return {}; } // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-34]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-31]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn ConvertFromStruct() -> Class { return {}; } @@ -52,7 +49,7 @@ fn MemberAccess(p: Class*) -> i32 { // 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. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-45]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ return (*p).n; @@ -61,7 +58,7 @@ fn MemberAccess(p: Class*) -> i32 { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:20: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn Copy(p: Class*) -> Class { // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-57]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-54]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn Copy(p: Class*) -> Class { @@ -72,7 +69,7 @@ fn Let(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: `let` binding has incomplete type `Class`. // CHECK:STDERR: let c: Class = *p; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-68]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-65]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ let c: Class = *p; @@ -85,7 +82,7 @@ fn TakeIncomplete(c: Class); // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:23: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn ReturnIncomplete() -> Class; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-81]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-78]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ fn ReturnIncomplete() -> Class; @@ -94,7 +91,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete(*p); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-90]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-87]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-19]]:1: Initializing parameter 1 of function declared here. @@ -105,7 +102,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete({}); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-101]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-98]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-30]]:1: Initializing parameter 1 of function declared here. @@ -152,22 +149,22 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertFromStruct() -> { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc44_43.1: type = struct_type {} -// CHECK:STDOUT: %.loc44_43.2: {} = struct_literal () +// CHECK:STDOUT: %.loc41_43.1: type = struct_type {} +// CHECK:STDOUT: %.loc41_43.2: {} = struct_literal () // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @MemberAccess(%p: Class*) -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc58: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc55: ref Class = dereference %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Copy(%p: Class*) -> { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc68: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc65: ref Class = dereference %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -175,7 +172,7 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc78: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc75: ref Class = dereference %p.ref // CHECK:STDOUT: %c: = bind_name "c", // CHECK:STDOUT: return // CHECK:STDOUT: } @@ -186,18 +183,20 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: // CHECK:STDOUT: fn @CallTakeIncomplete(%p: Class*) { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %TakeIncomplete.ref.loc103: = name_reference "TakeIncomplete", file.%TakeIncomplete +// CHECK:STDOUT: %TakeIncomplete.ref.loc100: = name_reference "TakeIncomplete", file.%TakeIncomplete // CHECK:STDOUT: %p.ref: Class* = name_reference "p", %p -// CHECK:STDOUT: %.loc103_18: ref Class = dereference %p.ref -// CHECK:STDOUT: %.loc103_17: type = tuple_type () -// CHECK:STDOUT: %TakeIncomplete.ref.loc114: = name_reference "TakeIncomplete", file.%TakeIncomplete -// CHECK:STDOUT: %.loc114: {} = struct_literal () +// CHECK:STDOUT: %.loc100_18: ref Class = dereference %p.ref +// CHECK:STDOUT: %.loc100_17.1: type = tuple_type () +// CHECK:STDOUT: %.loc100_17.2: init () = call %TakeIncomplete.ref.loc100() +// CHECK:STDOUT: %TakeIncomplete.ref.loc111: = name_reference "TakeIncomplete", file.%TakeIncomplete +// CHECK:STDOUT: %.loc111_19: {} = struct_literal () +// CHECK:STDOUT: %.loc111_17: init () = call %TakeIncomplete.ref.loc111() // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %ReturnIncomplete.ref: = name_reference "ReturnIncomplete", file.%ReturnIncomplete -// CHECK:STDOUT: %.loc118: init = call %ReturnIncomplete.ref() +// CHECK:STDOUT: %.loc115: init = call %ReturnIncomplete.ref() // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_method.carbon b/toolchain/check/testdata/class/fail_method.carbon index 5a71ebdd02de..45ac6da65cd0 100644 --- a/toolchain/check/testdata/class/fail_method.carbon +++ b/toolchain/check/testdata/class/fail_method.carbon @@ -5,53 +5,70 @@ // AUTOUPDATE class Class { - fn F[self: Class]() -> i32; - - var k: i32; + fn NoSelf(); + fn WithSelf[self: Class](); } -fn Class.F[self: Class]() -> i32 { - return self.k; -} +fn F(c: Class) { + c.NoSelf(); + c.WithSelf(); -fn Call(c: Class) -> i32 { - // CHECK:STDERR: fail_method.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `Call with implicit parameters`. - // CHECK:STDERR: return c.F(); - // CHECK:STDERR: ^ - return c.F(); + Class.NoSelf(); + // CHECK:STDERR: fail_method.carbon:[[@LINE+6]]:17: ERROR: Missing object argument in method call. + // CHECK:STDERR: Class.WithSelf(); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_method.carbon:[[@LINE-11]]:3: Calling function declared here. + // CHECK:STDERR: fn WithSelf[self: Class](); + // CHECK:STDERR: ^ + Class.WithSelf(); + // CHECK:STDERR: fail_method.carbon:[[@LINE+6]]:17: ERROR: 1 argument(s) passed to function expecting 0 argument(s). + // CHECK:STDERR: Class.WithSelf(c); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_method.carbon:[[@LINE-18]]:3: Calling function declared here. + // CHECK:STDERR: fn WithSelf[self: Class](); + // CHECK:STDERR: ^ + Class.WithSelf(c); } // CHECK:STDOUT: file "fail_method.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class -// CHECK:STDOUT: %.loc11: type = struct_type {.k: i32} +// CHECK:STDOUT: %.loc10: type = struct_type {} // CHECK:STDOUT: %F: = fn_decl @F -// CHECK:STDOUT: %Call: = fn_decl @Call // 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: %NoSelf: = fn_decl @NoSelf +// CHECK:STDOUT: %WithSelf: = fn_decl @WithSelf // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .F = %F -// CHECK:STDOUT: .k = %k +// CHECK:STDOUT: .NoSelf = %NoSelf +// CHECK:STDOUT: .WithSelf = %WithSelf // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F[%self: 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: } +// CHECK:STDOUT: fn @NoSelf(); // CHECK:STDOUT: -// CHECK:STDOUT: fn @Call(%c: Class) -> i32 { +// CHECK:STDOUT: fn @WithSelf[%self: Class](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%c: Class) { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %c.ref: Class = name_reference "c", %c -// CHECK:STDOUT: %F.ref: = name_reference "F", @Class.%F -// CHECK:STDOUT: return +// CHECK:STDOUT: %.loc10: type = tuple_type () +// CHECK:STDOUT: %.loc7: type = ptr_type {} +// CHECK:STDOUT: %c.ref.loc13: Class = name_reference "c", %c +// CHECK:STDOUT: %NoSelf.ref.loc13: = name_reference "NoSelf", @Class.%NoSelf +// CHECK:STDOUT: %.loc13: init () = call %NoSelf.ref.loc13() +// CHECK:STDOUT: %c.ref.loc14: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc14_4: = bound_method %c.ref.loc14, @Class.%WithSelf +// CHECK:STDOUT: %.loc14_13: init () = call %.loc14_4(%c.ref.loc14) +// CHECK:STDOUT: %Class.ref.loc16: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %NoSelf.ref.loc16: = name_reference "NoSelf", @Class.%NoSelf +// CHECK:STDOUT: %.loc16: init () = call %NoSelf.ref.loc16() +// CHECK:STDOUT: %Class.ref.loc23: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %WithSelf.ref.loc23: = name_reference "WithSelf", @Class.%WithSelf +// CHECK:STDOUT: %.loc23: init () = call %WithSelf.ref.loc23() +// CHECK:STDOUT: %Class.ref.loc30: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %WithSelf.ref.loc30: = name_reference "WithSelf", @Class.%WithSelf +// CHECK:STDOUT: %c.ref.loc30: Class = name_reference "c", %c +// CHECK:STDOUT: %.loc30: init () = call %WithSelf.ref.loc30() +// CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_redefinition.carbon b/toolchain/check/testdata/class/fail_redefinition.carbon index a6738b390086..638faa03fe45 100644 --- a/toolchain/check/testdata/class/fail_redefinition.carbon +++ b/toolchain/check/testdata/class/fail_redefinition.carbon @@ -39,8 +39,8 @@ fn Class.H() {} // CHECK:STDOUT: %H: = fn_decl @H // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .F = -// CHECK:STDOUT: .H = +// CHECK:STDOUT: .F = +// CHECK:STDOUT: .H = // CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_reorder.carbon b/toolchain/check/testdata/class/fail_reorder.carbon index 0ca5514b3b9c..8a716601b8b9 100644 --- a/toolchain/check/testdata/class/fail_reorder.carbon +++ b/toolchain/check/testdata/class/fail_reorder.carbon @@ -8,18 +8,15 @@ class Class { fn G() -> i32 { // TODO: This should find the member function `F` even though it's declared // later. - // CHECK:STDERR: fail_reorder.carbon:[[@LINE+12]]:12: ERROR: Member access into incomplete class `Class`. + // CHECK:STDERR: fail_reorder.carbon:[[@LINE+9]]:12: ERROR: Member access into incomplete class `Class`. // CHECK:STDERR: return Class.F(); // CHECK:STDERR: ^ // CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition. // CHECK:STDERR: class Class { // CHECK:STDERR: ^ - // CHECK:STDERR: fail_reorder.carbon:[[@LINE+6]]:17: ERROR: Name `F` not found. + // CHECK:STDERR: fail_reorder.carbon:[[@LINE+3]]:17: ERROR: Name `F` not found. // CHECK:STDERR: return Class.F(); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_reorder.carbon:[[@LINE+3]]:20: ERROR: Semantics TODO: `Not a callable name`. - // CHECK:STDERR: return Class.F(); - // CHECK:STDERR: ^ return Class.F(); } @@ -31,7 +28,7 @@ class Class { // CHECK:STDOUT: file "fail_reorder.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class -// CHECK:STDOUT: %.loc29: type = struct_type {} +// CHECK:STDOUT: %.loc26: type = struct_type {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { @@ -52,6 +49,6 @@ class Class { // CHECK:STDOUT: // CHECK:STDOUT: fn @F() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc27: i32 = int_literal 1 -// CHECK:STDOUT: return %.loc27 +// CHECK:STDOUT: %.loc24: i32 = int_literal 1 +// CHECK:STDOUT: return %.loc24 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon index fa039bb869b5..1b8a50ef3e6a 100644 --- a/toolchain/check/testdata/class/fail_scope.carbon +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -11,12 +11,9 @@ class Class { // TODO: This `F()` should find `Class.F`. fn G() -> i32 { - // CHECK:STDERR: fail_scope.carbon:[[@LINE+6]]:12: ERROR: Name `F` not found. + // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:12: ERROR: Name `F` not found. // CHECK:STDERR: return F(); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `Not a callable name`. - // CHECK:STDERR: return F(); - // CHECK:STDERR: ^ return F(); } } @@ -24,7 +21,7 @@ class Class { // CHECK:STDOUT: file "fail_scope.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class -// CHECK:STDOUT: %.loc22: type = struct_type {} +// CHECK:STDOUT: %.loc19: type = struct_type {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { diff --git a/toolchain/check/testdata/class/fail_self.carbon b/toolchain/check/testdata/class/fail_self.carbon index 238debb2725d..19e343b1cbb1 100644 --- a/toolchain/check/testdata/class/fail_self.carbon +++ b/toolchain/check/testdata/class/fail_self.carbon @@ -36,16 +36,33 @@ fn Class.G() -> Class { return self; } +class WrongSelf { + fn F[self: Class](); +} + +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: fn F[self: Class](); + // CHECK:STDERR: ^ + ws.F(); +} + // CHECK:STDOUT: file "fail_self.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: %.loc20: type = struct_type {} -// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %F: = fn_decl @F.1 // CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: class_declaration @WrongSelf, () +// CHECK:STDOUT: %WrongSelf: type = class_type @WrongSelf +// CHECK:STDOUT: %CallWrongSelf: = fn_decl @CallWrongSelf // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { -// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %F: = fn_decl @F.1 // CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: // CHECK:STDOUT: !members: @@ -53,7 +70,14 @@ fn Class.G() -> Class { // CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @F(%self: Class) { +// CHECK:STDOUT: class @WrongSelf { +// CHECK:STDOUT: %F: = fn_decl @F.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1(%self: Class) { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc20: type = tuple_type () // CHECK:STDOUT: %.loc7: type = ptr_type {} @@ -67,3 +91,13 @@ fn Class.G() -> Class { // CHECK:STDOUT: %self.ref: = name_reference "self", // CHECK:STDOUT: return // CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2[%self: Class](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallWrongSelf(%ws: WrongSelf) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %ws.ref: WrongSelf = name_reference "ws", %ws +// CHECK:STDOUT: %.loc50_5: = bound_method %ws.ref, @WrongSelf.%F +// CHECK:STDOUT: %.loc50_7: init () = call %.loc50_5() +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/method.carbon b/toolchain/check/testdata/class/method.carbon new file mode 100644 index 000000000000..51353d776c7d --- /dev/null +++ b/toolchain/check/testdata/class/method.carbon @@ -0,0 +1,60 @@ +// 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[self: Class]() -> i32; + + var k: i32; +} + +fn Class.F[self: Class]() -> i32 { + return self.k; +} + +fn Call(c: Class) -> i32 { + // TODO: The sem-ir for this call doesn't distinguish the `self` argument from + // the explicit arguments. + return c.F(); +} + +// 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: %F: = fn_decl @F +// CHECK:STDOUT: %Call: = fn_decl @Call +// 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: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .k = %k +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F[%self: 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: } +// 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: } diff --git a/toolchain/check/testdata/function/call/fail_not_callable.carbon b/toolchain/check/testdata/function/call/fail_not_callable.carbon index 4395006adb90..aff4100328fe 100644 --- a/toolchain/check/testdata/function/call/fail_not_callable.carbon +++ b/toolchain/check/testdata/function/call/fail_not_callable.carbon @@ -5,9 +5,9 @@ // AUTOUPDATE fn Run() { - // CHECK:STDERR: fail_not_callable.carbon:[[@LINE+3]]:24: ERROR: Semantics TODO: `Not a callable name`. + // CHECK:STDERR: fail_not_callable.carbon:[[@LINE+3]]:23: ERROR: Value of type `String` is not callable. // CHECK:STDERR: var x: i32 = "hello"(); - // CHECK:STDERR: ^ + // CHECK:STDERR: ^ var x: i32 = "hello"(); } diff --git a/toolchain/check/testdata/function/call/fail_param_count.carbon b/toolchain/check/testdata/function/call/fail_param_count.carbon index eef26363ce44..7f532a08b6b2 100644 --- a/toolchain/check/testdata/function/call/fail_param_count.carbon +++ b/toolchain/check/testdata/function/call/fail_param_count.carbon @@ -81,16 +81,22 @@ fn Main() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Run0.ref.loc18: = name_reference "Run0", file.%Run0 // CHECK:STDOUT: %.loc18_8: i32 = int_literal 1 -// CHECK:STDOUT: %.loc18_7: type = tuple_type () +// CHECK:STDOUT: %.loc18_7.1: type = tuple_type () +// CHECK:STDOUT: %.loc18_7.2: init () = call %Run0.ref.loc18() // CHECK:STDOUT: %Run0.ref.loc25: = name_reference "Run0", file.%Run0 // CHECK:STDOUT: %.loc25_8: i32 = int_literal 0 // CHECK:STDOUT: %.loc25_11: i32 = int_literal 1 +// CHECK:STDOUT: %.loc25_7: init () = call %Run0.ref.loc25() // CHECK:STDOUT: %Run1.ref.loc33: = name_reference "Run1", file.%Run1 +// CHECK:STDOUT: %.loc33: init () = call %Run1.ref.loc33() // CHECK:STDOUT: %Run1.ref.loc40: = name_reference "Run1", file.%Run1 // CHECK:STDOUT: %.loc40_8: i32 = int_literal 0 // CHECK:STDOUT: %.loc40_11: i32 = int_literal 1 +// CHECK:STDOUT: %.loc40_7: init () = call %Run1.ref.loc40() // CHECK:STDOUT: %Run2.ref.loc48: = name_reference "Run2", file.%Run2 +// CHECK:STDOUT: %.loc48: init () = call %Run2.ref.loc48() // CHECK:STDOUT: %Run2.ref.loc55: = name_reference "Run2", file.%Run2 -// CHECK:STDOUT: %.loc55: i32 = int_literal 0 +// CHECK:STDOUT: %.loc55_8: i32 = int_literal 0 +// CHECK:STDOUT: %.loc55_7: init () = call %Run2.ref.loc55() // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/function/call/fail_param_type.carbon b/toolchain/check/testdata/function/call/fail_param_type.carbon index 57b09fdb19be..be0c4d87b6ac 100644 --- a/toolchain/check/testdata/function/call/fail_param_type.carbon +++ b/toolchain/check/testdata/function/call/fail_param_type.carbon @@ -30,6 +30,7 @@ fn F() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %G.ref: = name_reference "G", file.%G // CHECK:STDOUT: %.loc16_5: f64 = real_literal 10e-1 -// CHECK:STDOUT: %.loc16_4: type = tuple_type () +// CHECK:STDOUT: %.loc16_4.1: type = tuple_type () +// CHECK:STDOUT: %.loc16_4.2: init () = call %G.ref() // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 666bc8019cd4..55a584b819f3 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -109,6 +109,14 @@ CARBON_DIAGNOSTIC_KIND(ParametersRequiredByIntroducer) CARBON_DIAGNOSTIC_KIND(SemanticsTodo) +// Function call checking. +CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch) +CARBON_DIAGNOSTIC_KIND(CallToNonCallable) +CARBON_DIAGNOSTIC_KIND(InCallToFunction) +CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam) +CARBON_DIAGNOSTIC_KIND(InCallToFunctionSelf) +CARBON_DIAGNOSTIC_KIND(MissingObjectInMethodCall) + CARBON_DIAGNOSTIC_KIND(AddressOfEphemeralReference) CARBON_DIAGNOSTIC_KIND(AddressOfNonReference) CARBON_DIAGNOSTIC_KIND(AddrOnNonSelfParameter) @@ -128,9 +136,6 @@ CARBON_DIAGNOSTIC_KIND(FunctionRedefinition) CARBON_DIAGNOSTIC_KIND(NameNotFound) CARBON_DIAGNOSTIC_KIND(NameDeclarationDuplicate) CARBON_DIAGNOSTIC_KIND(NameDeclarationPrevious) -CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch) -CARBON_DIAGNOSTIC_KIND(InCallToFunction) -CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam) CARBON_DIAGNOSTIC_KIND(MissingReturnStatement) CARBON_DIAGNOSTIC_KIND(RepeatedConst) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInConversion) diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index fce28b6035d9..6b1ffe5c7aef 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -219,6 +219,7 @@ auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* { // (`i8`) versus for `bool` values (`i1`). return llvm::Type::getInt1Ty(*llvm_context_); case SemIR::BuiltinKind::FunctionType.AsInt(): + case SemIR::BuiltinKind::BoundMethodType.AsInt(): case SemIR::BuiltinKind::NamespaceType.AsInt(): // Return an empty struct as a placeholder. return llvm::StructType::get(*llvm_context_); diff --git a/toolchain/lower/function_context.cpp b/toolchain/lower/function_context.cpp index a4af95f4c9ce..1228f138dbca 100644 --- a/toolchain/lower/function_context.cpp +++ b/toolchain/lower/function_context.cpp @@ -78,6 +78,18 @@ auto FunctionContext::CreateSyntheticBlock() -> llvm::BasicBlock* { return synthetic_block_; } +auto FunctionContext::GetLocalOrGlobal(SemIR::NodeId node_id) -> llvm::Value* { + auto target = sem_ir().nodes().Get(node_id); + if (auto function_decl = target.TryAs()) { + return GetFunction(function_decl->function_id); + } + if (auto class_type = target.TryAs()) { + return GetTypeAsValue(); + } + // TODO: Handle other kinds of name references to globals. + return GetLocal(node_id); +} + auto FunctionContext::FinishInitialization(SemIR::TypeId type_id, SemIR::NodeId dest_id, SemIR::NodeId source_id) -> void { diff --git a/toolchain/lower/function_context.h b/toolchain/lower/function_context.h index e881f955bce1..fbf710370d7d 100644 --- a/toolchain/lower/function_context.h +++ b/toolchain/lower/function_context.h @@ -59,6 +59,9 @@ class FunctionContext { << sem_ir().nodes().Get(node_id); } + // Returns a value for the given node, which might not be local. + auto GetLocalOrGlobal(SemIR::NodeId node_id) -> llvm::Value*; + // Gets a callable's function. auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* { return file_context_->GetFunction(function_id); diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 3f95488aa19a..ed81011ac449 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -73,6 +73,13 @@ auto HandleBoolLiteral(FunctionContext& context, SemIR::NodeId node_id, context.SetLocal(node_id, v); } +auto HandleBoundMethod(FunctionContext& context, SemIR::NodeId node_id, + SemIR::BoundMethod node) -> void { + // Propagate just the function; the object is separately provided to the + // enclosing call as an implicit argument. + context.SetLocal(node_id, context.GetLocalOrGlobal(node.function_id)); +} + auto HandleBranch(FunctionContext& context, SemIR::NodeId /*node_id*/, SemIR::Branch node) -> void { // Opportunistically avoid creating a BasicBlock that contains just a branch. @@ -314,15 +321,7 @@ auto HandleNameReference(FunctionContext& context, SemIR::NodeId node_id, return; } - auto target = context.sem_ir().nodes().Get(node.value_id); - if (auto function_decl = target.TryAs()) { - context.SetLocal(node_id, context.GetFunction(function_decl->function_id)); - } else if (auto class_type = target.TryAs()) { - context.SetLocal(node_id, context.GetTypeAsValue()); - } else { - // TODO: Handle other kinds of name references to globals. - context.SetLocal(node_id, context.GetLocal(node.value_id)); - } + context.SetLocal(node_id, context.GetLocalOrGlobal(node.value_id)); } auto HandleNamespace(FunctionContext& /*context*/, SemIR::NodeId /*node_id*/, diff --git a/toolchain/lower/testdata/class/method.carbon b/toolchain/lower/testdata/class/method.carbon new file mode 100644 index 000000000000..cee47b9d5ce9 --- /dev/null +++ b/toolchain/lower/testdata/class/method.carbon @@ -0,0 +1,33 @@ +// 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; + + fn Get[self: C]() -> i32; + fn Set[addr self: C*](n: i32); +} + +fn F(p: C*) { + let n: i32 = (*p).Get(); + // TODO: Support `addr self`. + // (*p).Set(n); +} + +// CHECK:STDOUT: ; ModuleID = 'method.carbon' +// CHECK:STDOUT: source_filename = "method.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare i32 @Get(ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @Set(ptr, i32) +// CHECK:STDOUT: +// CHECK:STDOUT: define void @F(ptr %p) { +// CHECK:STDOUT: %Get = call i32 @Get(ptr %p) +// 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: ret void +// CHECK:STDOUT: } diff --git a/toolchain/sem_ir/builtin_kind.def b/toolchain/sem_ir/builtin_kind.def index ac3bb58664c5..852f5b562aff 100644 --- a/toolchain/sem_ir/builtin_kind.def +++ b/toolchain/sem_ir/builtin_kind.def @@ -67,6 +67,9 @@ CARBON_SEM_IR_BUILTIN_KIND(StringType, "String") // The type of function values. CARBON_SEM_IR_BUILTIN_KIND(FunctionType, "") +// The type of bound method values. +CARBON_SEM_IR_BUILTIN_KIND(BoundMethodType, "") + // The type of namespace names. CARBON_SEM_IR_BUILTIN_KIND(NamespaceType, "") diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 604b65da1b9c..091235ff45e5 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -191,6 +191,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case BindValue::Kind: case BlockArg::Kind: case BoolLiteral::Kind: + case BoundMethod::Kind: case Branch::Kind: case BranchIf::Kind: case BranchWithArg::Kind: @@ -389,6 +390,7 @@ auto File::StringifyTypeExpression(NodeId outer_node_id, case BindValue::Kind: case BlockArg::Kind: case BoolLiteral::Kind: + case BoundMethod::Kind: case Branch::Kind: case BranchIf::Kind: case BranchWithArg::Kind: @@ -491,6 +493,7 @@ auto GetExpressionCategory(const File& file, NodeId node_id) case BindValue::Kind: case BlockArg::Kind: case BoolLiteral::Kind: + case BoundMethod::Kind: case ClassType::Kind: case ConstType::Kind: case IntegerLiteral::Kind: diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 39f8559e585b..eaeb0677f2fb 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -743,6 +743,11 @@ class Formatter { out_ << " "; FormatArg(node.callee_id); + if (!node.args_id.is_valid()) { + out_ << "()"; + return; + } + llvm::ArrayRef args = sem_ir_.node_blocks().Get(node.args_id); bool has_return_slot = diff --git a/toolchain/sem_ir/node_kind.def b/toolchain/sem_ir/node_kind.def index 771f4b931325..26f1440db797 100644 --- a/toolchain/sem_ir/node_kind.def +++ b/toolchain/sem_ir/node_kind.def @@ -27,6 +27,7 @@ CARBON_SEM_IR_NODE_KIND(BindName) CARBON_SEM_IR_NODE_KIND(BindValue) CARBON_SEM_IR_NODE_KIND(BlockArg) CARBON_SEM_IR_NODE_KIND(BoolLiteral) +CARBON_SEM_IR_NODE_KIND(BoundMethod) CARBON_SEM_IR_NODE_KIND(Branch) CARBON_SEM_IR_NODE_KIND(BranchIf) CARBON_SEM_IR_NODE_KIND(BranchWithArg) diff --git a/toolchain/sem_ir/typed_nodes.h b/toolchain/sem_ir/typed_nodes.h index fd2e69ec1f4e..47dc46e44750 100644 --- a/toolchain/sem_ir/typed_nodes.h +++ b/toolchain/sem_ir/typed_nodes.h @@ -129,6 +129,20 @@ struct BoolLiteral { BoolValue value; }; +// A bound method, that combines a function with the value to use for its +// `self` parameter, such as `object.MethodName`. +struct BoundMethod { + static constexpr auto Kind = NodeKind::BoundMethod.Define("bound_method"); + + Parse::Node parse_node; + TypeId type_id; + // The object argument in the bound method, which will be used to initialize + // `self`, or whose address will be used to initialize `self` for an `addr + // self` parameter. + NodeId object_id; + NodeId function_id; +}; + struct Branch { static constexpr auto Kind = NodeKind::Branch.Define("br", TerminatorKind::Terminator); @@ -172,6 +186,10 @@ struct Call { Parse::Node parse_node; TypeId type_id; NodeId callee_id; + // The arguments block contains IDs for the following arguments, in order: + // - The argument for each implicit parameter. + // - The argument for each explicit parameter. + // - The argument for the return slot, if present. NodeBlockId args_id; };