From 43ffd721a468fbe4f54255e2a228284d84887e1c Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Tue, 11 Nov 2025 12:30:19 -0800 Subject: [PATCH] Support `ref` tags on arguments to `ref` params (#6312) The issue of whether/how to include `ref` tags in the textual and in-memory SemIR (see discussion [here](https://discord.com/channels/655572317891461132/655578254970716160/1431316355742961805)) is left as future work. --------- Co-authored-by: Richard Smith --- toolchain/check/call.cpp | 4 +- toolchain/check/context.cpp | 14 + toolchain/check/context.h | 14 + toolchain/check/convert.cpp | 48 +++- toolchain/check/convert.h | 5 + toolchain/check/handle_call_expr.cpp | 22 +- toolchain/check/handle_let_and_var.cpp | 2 +- toolchain/check/import_ref.cpp | 3 + toolchain/check/merge.cpp | 3 +- toolchain/check/node_stack.h | 1 - toolchain/check/operator.cpp | 7 + toolchain/check/pattern_match.cpp | 51 ++-- .../check/testdata/class/fail_ref_self.carbon | 3 +- .../check/testdata/function/call/ref.carbon | 253 ++++++++++++++++++ ...pecialization_with_symbolic_rewrite.carbon | 6 +- toolchain/check/testdata/let/ref.carbon | 206 -------------- .../check/testdata/var/var_pattern.carbon | 8 +- toolchain/check/thunk.cpp | 3 +- toolchain/diagnostics/diagnostic_kind.def | 4 + toolchain/lower/file_context.cpp | 3 +- .../testdata/function/call/ref_param.carbon | 2 +- toolchain/parse/handle_brace_expr.cpp | 5 + toolchain/parse/handle_call_expr.cpp | 42 --- toolchain/parse/handle_paren_expr.cpp | 59 +++- toolchain/parse/node_kind.def | 3 +- toolchain/parse/state.def | 67 +++-- .../testdata/basics/function_call.carbon | 2 +- toolchain/parse/testdata/function/call.carbon | 201 ++++++++++++++ .../parse/testdata/function/definition.carbon | 2 +- .../fail_postfix_space_before_comma.carbon | 2 +- .../testdata/operators/fixity_in_call.carbon | 2 +- toolchain/parse/typed_nodes.h | 13 +- toolchain/sem_ir/expr_info.cpp | 1 + toolchain/sem_ir/file.cpp | 37 +++ toolchain/sem_ir/file.h | 6 + toolchain/sem_ir/inst_categories.h | 7 +- toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/inst_namer.cpp | 3 +- toolchain/sem_ir/typed_insts.h | 20 +- 39 files changed, 798 insertions(+), 337 deletions(-) create mode 100644 toolchain/check/testdata/function/call/ref.carbon delete mode 100644 toolchain/parse/handle_call_expr.cpp create mode 100644 toolchain/parse/testdata/function/call.carbon diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index b759f5d58f8f..21799490572b 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -300,8 +300,6 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id, } case SemIR::Function::SpecialFunctionKind::HasCppThunk: { - // This recurses back into `PerformCall`. However, we never form a C++ - // thunk to a C++ thunk, so we only recurse once. return PerformCppThunkCall(context, loc_id, callee_function.function_id, context.inst_blocks().Get(converted_args_id), callee.cpp_thunk_decl_id()); @@ -359,6 +357,7 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id, return SemIR::ErrorInst::InstId; } case CARBON_KIND(SemIR::CalleeFunction fn): { + context.ref_tags().Insert(fn.self_id, Context::RefTag::NotRequired); return PerformCallToFunction(context, loc_id, callee_id, fn, arg_ids); } case CARBON_KIND(SemIR::CalleeNonFunction _): { @@ -366,6 +365,7 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id, } case CARBON_KIND(SemIR::CalleeCppOverloadSet overload): { + context.ref_tags().Insert(overload.self_id, Context::RefTag::NotRequired); return PerformCallToCppFunction(context, loc_id, overload.cpp_overload_set_id, overload.self_id, arg_ids); diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 28ac2de673f6..528a1b14732e 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -8,6 +8,7 @@ #include #include "common/check.h" +#include "toolchain/base/kind_switch.h" #include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/sem_ir/ids.h" @@ -77,6 +78,19 @@ auto Context::VerifyOnFinish() const -> void { CARBON_FATAL("{0}Built invalid semantics IR: {1}\n", sem_ir_, verify.error()); } + + if (!sem_ir_->has_errors()) { + auto ref_tags_needed = sem_ir_->CollectRefTagsNeeded(); + + ref_tags_.ForEach([&ref_tags_needed](SemIR::InstId inst_id, RefTag kind) { + CARBON_CHECK( + ref_tags_needed.Erase(inst_id) || kind == RefTag::NotRequired, + "Inst has unnecessary `ref` tag: {0}", inst_id); + }); + ref_tags_needed.ForEach([this](SemIR::InstId inst_id) { + CARBON_FATAL("Inst missing `ref` tag: {0}", insts().Get(inst_id)); + }); + } #endif } diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 31122c20c7c0..c69b30ef04e4 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -193,6 +193,13 @@ class Context { return var_storage_map_; } + enum class RefTag { Present, NotRequired }; + + auto ref_tags() -> Map& { return ref_tags_; } + auto ref_tags() const -> const Map& { + return ref_tags_; + } + // During Choice typechecking, each alternative turns into a name binding on // the Choice type, but this can't be done until the full Choice type is // known. This represents each binding to be done at the end of checking the @@ -445,6 +452,13 @@ class Context { // processing the enclosing full-pattern. Map var_storage_map_; + // Insts in this map are syntactically permitted to be bound to a reference + // parameter, either because they've been explicitly tagged with `ref` in the + // source code, or because they appear in a position where that tag is not + // required, such as an operator operand (the RefTag value indicates which + // of those is the case). + Map ref_tags_; + // Each alternative in a Choice gets an entry here, they are stored in // declaration order. The vector is consumed and emptied at the end of the // Choice definition. diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 1a96537ae3a2..d274e792c52a 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -738,6 +738,10 @@ static auto IsValidExprCategoryForConversionTarget( category == SemIR::ExprCategory::DurableRef || category == SemIR::ExprCategory::EphemeralRef || category == SemIR::ExprCategory::Initializing; + case ConversionTarget::RefParam: + return category == SemIR::ExprCategory::DurableRef || + category == SemIR::ExprCategory::EphemeralRef || + category == SemIR::ExprCategory::Initializing; case ConversionTarget::DurableRef: return category == SemIR::ExprCategory::DurableRef; case ConversionTarget::CppThunkRef: @@ -1381,6 +1385,28 @@ auto PerformAction(Context& context, SemIR::LocId loc_id, action.target_type_inst_id)}); } +// Diagnoses a missing or unnecessary `ref` tag when converting `expr_id` to +// `target`, and returns whether a `ref` tag is present. +static auto CheckRefTag(Context& context, SemIR::InstId expr_id, + ConversionTarget target) -> bool { + if (auto lookup_result = context.ref_tags().Lookup(expr_id)) { + if (lookup_result.value() == Context::RefTag::Present && + target.kind != ConversionTarget::RefParam) { + CARBON_DIAGNOSTIC(RefTagNoRefParam, Error, + "`ref` tag is not an argument to a `ref` parameter"); + context.emitter().Emit(expr_id, RefTagNoRefParam); + } + return true; + } else { + if (target.kind == ConversionTarget::RefParam) { + CARBON_DIAGNOSTIC(RefParamNoRefTag, Error, + "argument to `ref` parameter not marked with `ref`"); + context.emitter().Emit(expr_id, RefParamNoRefTag); + } + return false; + } +} + auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, ConversionTarget target, SemIR::ClassType* vtable_class_type) -> SemIR::InstId { @@ -1406,6 +1432,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, return SemIR::ErrorInst::InstId; } + bool has_ref_tag = CheckRefTag(context, expr_id, target); + // We can only perform initialization for complete, non-abstract types. Note // that `RequireConcreteType` returns true for facet types, since their // representation is fixed. This allows us to support using the `Self` of an @@ -1538,6 +1566,9 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, {.type_id = target.type_id, .original_id = orig_expr_id, .result_id = expr_id}); + if (has_ref_tag) { + context.ref_tags().Insert(expr_id, Context::RefTag::NotRequired); + } } // For `as`, don't perform any value category conversions. In particular, an @@ -1592,7 +1623,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, // If a reference expression is an acceptable result, we're done. if (target.kind == ConversionTarget::ValueOrRef || target.kind == ConversionTarget::Discarded || - target.kind == ConversionTarget::CppThunkRef) { + target.kind == ConversionTarget::CppThunkRef || + target.kind == ConversionTarget::RefParam) { break; } @@ -1617,6 +1649,20 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, } return SemIR::ErrorInst::InstId; } + if (target.kind == ConversionTarget::RefParam) { + // Don't diagnose a non-reference scrutinee if it has a user-written + // `ref` tag, because that's diagnosed in `CheckRefTag`. + if (target.diagnose) { + if (auto lookup_result = context.ref_tags().Lookup(expr_id); + !lookup_result || + lookup_result.value() != Context::RefTag::Present) { + CARBON_DIAGNOSTIC(ValueForRefParam, Error, + "value expression passed to reference parameter"); + context.emitter().Emit(loc_id, ValueForRefParam); + } + } + return SemIR::ErrorInst::InstId; + } // When initializing from a value, perform a copy. if (target.is_initializer()) { diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 384df7974a1a..254863a37620 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -21,6 +21,11 @@ struct ConversionTarget { ValueOrRef, // Convert to a durable reference of type `type_id`. DurableRef, + // Convert to a reference, suitable for binding to a reference parameter. + // This allows both durable and ephemeral references. The restriction that + // only a `ref self` parameter can bind to an ephemeral reference is + // enforced separately when handling `ref` tags on call arguments. + RefParam, // Convert to a reference of type `type_id`, for use as the argument to a // C++ thunk. CppThunkRef, diff --git a/toolchain/check/handle_call_expr.cpp b/toolchain/check/handle_call_expr.cpp index 420313bb704b..9ce520abbf4a 100644 --- a/toolchain/check/handle_call_expr.cpp +++ b/toolchain/check/handle_call_expr.cpp @@ -5,6 +5,8 @@ #include "toolchain/check/call.h" #include "toolchain/check/context.h" #include "toolchain/check/handle.h" +#include "toolchain/check/inst.h" +#include "toolchain/sem_ir/expr_info.h" #include "toolchain/sem_ir/inst.h" namespace Carbon::Check { @@ -16,12 +18,6 @@ auto HandleParseNode(Context& context, Parse::CallExprStartId node_id) -> bool { return true; } -auto HandleParseNode(Context& context, Parse::CallExprCommaId /*node_id*/) - -> bool { - context.param_and_arg_refs_stack().ApplyComma(); - return true; -} - auto HandleParseNode(Context& context, Parse::CallExprId node_id) -> bool { // Process the final explicit call argument now, but leave the arguments // block on the stack until the end of this function. @@ -37,4 +33,18 @@ auto HandleParseNode(Context& context, Parse::CallExprId node_id) -> bool { return true; } +auto HandleParseNode(Context& context, Parse::RefTagId node_id) -> bool { + auto expr_id = context.node_stack().Peek(); + + if (SemIR::GetExprCategory(context.sem_ir(), expr_id) != + SemIR::ExprCategory::DurableRef) { + CARBON_DIAGNOSTIC( + RefTagNotDurableRef, Error, + "expression tagged with `ref` is not a durable reference"); + context.emitter().Emit(node_id, RefTagNotDurableRef); + } + context.ref_tags().Insert(expr_id, Context::RefTag::Present); + return true; +} + } // namespace Carbon::Check diff --git a/toolchain/check/handle_let_and_var.cpp b/toolchain/check/handle_let_and_var.cpp index 61105cb269d3..4309266e0038 100644 --- a/toolchain/check/handle_let_and_var.cpp +++ b/toolchain/check/handle_let_and_var.cpp @@ -127,7 +127,7 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id) switch (context.full_pattern_stack().CurrentKind()) { case FullPatternStack::Kind::ExplicitParamList: case FullPatternStack::Kind::ImplicitParamList: - subpattern_id = AddPatternInst( + subpattern_id = AddPatternInst( context, node_id, {.type_id = type_id, .subpattern_id = subpattern_id, diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 858a089799d1..6e7ff09d0f90 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -3570,6 +3570,9 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver, case CARBON_KIND(SemIR::ValueParamPattern inst): { return TryResolveTypedInst(resolver, inst, constant_inst_id); } + case CARBON_KIND(SemIR::VarParamPattern inst): { + return TryResolveTypedInst(resolver, inst, constant_inst_id); + } case CARBON_KIND(SemIR::VarPattern inst): { return TryResolveTypedInst(resolver, inst, constant_inst_id); } diff --git a/toolchain/check/merge.cpp b/toolchain/check/merge.cpp index 5d71e7ac62fb..1c6cbfdd2886 100644 --- a/toolchain/check/merge.cpp +++ b/toolchain/check/merge.cpp @@ -257,7 +257,8 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, } case SemIR::OutParamPattern::Kind: case SemIR::RefParamPattern::Kind: - case SemIR::ValueParamPattern::Kind: { + case SemIR::ValueParamPattern::Kind: + case SemIR::VarParamPattern::Kind: { pattern_stack.push_back( {.prev_id = prev_param_pattern.As().subpattern_id, diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 1f98eb14f009..b783c3cce076 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -485,7 +485,6 @@ class NodeStack { case Parse::NodeKind::BaseColon: case Parse::NodeKind::BaseIntroducer: case Parse::NodeKind::BreakStatementStart: - case Parse::NodeKind::CallExprComma: case Parse::NodeKind::ChoiceAlternativeListComma: case Parse::NodeKind::CodeBlock: case Parse::NodeKind::CompileTimeBindingPatternStart: diff --git a/toolchain/check/operator.cpp b/toolchain/check/operator.cpp index 9ff645846bda..8eaebdbb7ab6 100644 --- a/toolchain/check/operator.cpp +++ b/toolchain/check/operator.cpp @@ -64,6 +64,9 @@ auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op, return SemIR::ErrorInst::InstId; } + // Operator operands don't require `ref` tags. + context.ref_tags().Insert(operand_id, Context::RefTag::NotRequired); + // For unary operators with a C++ class as the operand, try to import and call // the C++ operator. // TODO: Change impl lookup instead. See @@ -102,6 +105,10 @@ auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op, return SemIR::ErrorInst::InstId; } + // Operator operands don't require `ref` tags. + context.ref_tags().Insert(lhs_id, Context::RefTag::NotRequired); + context.ref_tags().Insert(rhs_id, Context::RefTag::NotRequired); + // For binary operators with a C++ class as at least one of the operands, try // to import and call the C++ operator. // TODO: Instead of hooking this here, change impl lookup, so that a generic diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 7025a0a27104..91c81d4ec094 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -103,9 +103,11 @@ class MatchContext { auto DoEmitPatternMatch(Context& context, SemIR::ValueParamPattern param_pattern, WorkItem entry) -> void; - auto DoEmitPatternMatch(Context& context, - SemIR::RefParamPattern param_pattern, WorkItem entry) - -> void; + template + requires std::is_same_v || + std::is_same_v + auto DoEmitPatternMatch(Context& context, RefParamPatternT param_pattern, + WorkItem entry) -> void; auto DoEmitPatternMatch(Context& context, SemIR::OutParamPattern param_pattern, WorkItem entry) -> void; @@ -337,8 +339,11 @@ auto MatchContext::DoEmitPatternMatch(Context& context, } } +template + requires std::is_same_v || + std::is_same_v auto MatchContext::DoEmitPatternMatch(Context& context, - SemIR::RefParamPattern param_pattern, + RefParamPatternT param_pattern, WorkItem entry) -> void { switch (kind_) { case MatchKind::Caller: { @@ -348,31 +353,17 @@ auto MatchContext::DoEmitPatternMatch(Context& context, param_pattern.index.index); CARBON_CHECK(entry.scrutinee_id.has_value()); - // TODO: If this is a `ref` pattern and !entry.is_self, require the - // scrutinee to have a `ref` tag. - - auto scrutinee_ref_id = ConvertToValueOrRefOfType( - context, SemIR::LocId(entry.scrutinee_id), entry.scrutinee_id, - ExtractScrutineeType( - context.sem_ir(), - SemIR::GetTypeOfInstInSpecific( - context.sem_ir(), callee_specific_id_, entry.pattern_id))); - - switch (SemIR::GetExprCategory(context.sem_ir(), scrutinee_ref_id)) { - case SemIR::ExprCategory::Error: - case SemIR::ExprCategory::DurableRef: - case SemIR::ExprCategory::EphemeralRef: - break; - default: - CARBON_DIAGNOSTIC(ValueForRefParam, Error, - "value expression passed to reference parameter"); - context.emitter().Emit(entry.scrutinee_id, ValueForRefParam); - // Add fake reference expression to preserve invariants. - auto scrutinee = context.insts().GetWithLocId(entry.scrutinee_id); - scrutinee_ref_id = AddInst( - context, scrutinee.loc_id, {.type_id = scrutinee.inst.type_id()}); + if (std::is_same_v) { + results_.push_back(entry.scrutinee_id); + break; } - results_.push_back(scrutinee_ref_id); + auto scrutinee_type_id = ExtractScrutineeType( + context.sem_ir(), + SemIR::GetTypeOfInstInSpecific(context.sem_ir(), callee_specific_id_, + entry.pattern_id)); + results_.push_back(Convert( + context, SemIR::LocId(entry.scrutinee_id), entry.scrutinee_id, + {.kind = ConversionTarget::RefParam, .type_id = scrutinee_type_id})); // Do not traverse farther, because the caller side of the pattern // ends here. break; @@ -627,6 +618,10 @@ auto MatchContext::EmitPatternMatch(Context& context, DoEmitPatternMatch(context, param_pattern, entry); break; } + case CARBON_KIND(SemIR::VarParamPattern param_pattern): { + DoEmitPatternMatch(context, param_pattern, entry); + break; + } case CARBON_KIND(SemIR::OutParamPattern param_pattern): { DoEmitPatternMatch(context, param_pattern, entry); break; diff --git a/toolchain/check/testdata/class/fail_ref_self.carbon b/toolchain/check/testdata/class/fail_ref_self.carbon index 790622b06019..56dd4c840839 100644 --- a/toolchain/check/testdata/class/fail_ref_self.carbon +++ b/toolchain/check/testdata/class/fail_ref_self.carbon @@ -129,8 +129,7 @@ fn F(c: Class, p: Class*) { // CHECK:STDOUT: %c.ref: %Class = name_ref c, %c // CHECK:STDOUT: %F.ref.loc29: %Class.F.type = name_ref F, @Class.%Class.F.decl [concrete = constants.%Class.F] // CHECK:STDOUT: %Class.F.bound.loc29: = bound_method %c.ref, %F.ref.loc29 -// CHECK:STDOUT: %.loc29: ref %Class = temporary_storage -// CHECK:STDOUT: %Class.F.call.loc29: init %empty_tuple.type = call %Class.F.bound.loc29(%.loc29) +// CHECK:STDOUT: %Class.F.call.loc29: init %empty_tuple.type = call %Class.F.bound.loc29() // CHECK:STDOUT: %p.ref: %ptr.e71 = name_ref p, %p // CHECK:STDOUT: %.loc32: ref %Class = deref %p.ref // CHECK:STDOUT: %F.ref.loc32: %Class.F.type = name_ref F, @Class.%Class.F.decl [concrete = constants.%Class.F] diff --git a/toolchain/check/testdata/function/call/ref.carbon b/toolchain/check/testdata/function/call/ref.carbon new file mode 100644 index 000000000000..a203f1763379 --- /dev/null +++ b/toolchain/check/testdata/function/call/ref.carbon @@ -0,0 +1,253 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// EXTRA-ARGS: --dump-sem-ir-ranges=only +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/call/ref.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/call/ref.carbon + +// --- basics.carbon + +library "[[@TEST_NAME]]"; + +//@dump-sem-ir-begin +fn F(ref x: i32); + +fn G() { + var y: i32 = 0; + + F(ref y); +} +//@dump-sem-ir-end + +// --- class.carbon + +library "[[@TEST_NAME]]"; + +//@dump-sem-ir-begin +class C { + fn F[ref self: Self](); +} + +fn G() { + var c: C; + + c.F(); +} +//@dump-sem-ir-end + +// --- fail_ref_not_durable_ref.carbon + +library "[[@TEST_NAME]]"; + +fn F(ref x: i32); + +fn G() { + let y: i32 = 0; + // CHECK:STDERR: fail_ref_not_durable_ref.carbon:[[@LINE+4]]:5: error: expression tagged with `ref` is not a durable reference [RefTagNotDurableRef] + // CHECK:STDERR: F(ref y); + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: + F(ref y); +} + +// --- fail_missing_ref.carbon + +library "[[@TEST_NAME]]"; + +fn F(ref x: i32); + +fn G() { + var y: i32 = 0; + // CHECK:STDERR: fail_missing_ref.carbon:[[@LINE+7]]:5: error: argument to `ref` parameter not marked with `ref` [RefParamNoRefTag] + // CHECK:STDERR: F(y); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_missing_ref.carbon:[[@LINE-7]]:6: note: initializing function parameter [InCallToFunctionParam] + // CHECK:STDERR: fn F(ref x: i32); + // CHECK:STDERR: ^~~~~~~~~~ + // CHECK:STDERR: + F(y); +} + +// --- fail_unnecessary_ref.carbon + +library "[[@TEST_NAME]]"; + +fn F(x: i32); + +fn G() { + var y: i32 = 0; + + // CHECK:STDERR: fail_unnecessary_ref.carbon:[[@LINE+4]]:24: error: `ref` tag is not an argument to a `ref` parameter [RefTagNoRefParam] + // CHECK:STDERR: var z: (i32,) = (ref y,); + // CHECK:STDERR: ^ + // CHECK:STDERR: + var z: (i32,) = (ref y,); + // CHECK:STDERR: fail_unnecessary_ref.carbon:[[@LINE+7]]:9: error: `ref` tag is not an argument to a `ref` parameter [RefTagNoRefParam] + // CHECK:STDERR: F(ref y); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_unnecessary_ref.carbon:[[@LINE-13]]:6: note: initializing function parameter [InCallToFunctionParam] + // CHECK:STDERR: fn F(x: i32); + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + F(ref y); +} + +// CHECK:STDOUT: --- basics.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %pattern_type.7ce: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %int_0.5c6: Core.IntLiteral = int_value 0 [concrete] +// CHECK:STDOUT: %ImplicitAs.type.d14: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] +// CHECK:STDOUT: %ImplicitAs.Convert.type.1b6: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%i32) [concrete] +// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51 = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.impl_witness.bc9: = impl_witness imports.%ImplicitAs.impl_witness_table.132, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.d14 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.bc9) [concrete] +// CHECK:STDOUT: %.322: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %bound_method: = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_0.6a9: %i32 = int_value 0 [concrete] +// CHECK:STDOUT: %type_where: type = facet_type > [concrete] +// CHECK:STDOUT: %facet_value: %type_where = facet_value %i32, () [concrete] +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.d4e: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value) [concrete] +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.424: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.d4e = struct_value () [concrete] +// CHECK:STDOUT: %ptr.235: type = ptr_type %i32 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.132 = impl_witness_table (%Core.import_ref.e24), @Core.IntLiteral.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %x.patt: %pattern_type.7ce = ref_binding_pattern x [concrete] +// CHECK:STDOUT: %x.param_patt: %pattern_type.7ce = ref_param_pattern %x.patt, call_param0 [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %x.param: ref %i32 = ref_param call_param0 +// CHECK:STDOUT: %.loc5: type = splice_block %i32 [concrete = constants.%i32] { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] +// CHECK:STDOUT: } +// CHECK:STDOUT: %x: ref %i32 = ref_binding x, %x.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%x.param: %i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %y.patt: %pattern_type.7ce = ref_binding_pattern y [concrete] +// CHECK:STDOUT: %y.var_patt: %pattern_type.7ce = var_pattern %y.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %y.var: ref %i32 = var %y.var_patt +// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6] +// CHECK:STDOUT: %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b] +// CHECK:STDOUT: %bound_method.loc8_3.1: = bound_method %int_0, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] +// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc8_3.2: = bound_method %int_0, %specific_fn [concrete = constants.%bound_method] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc8_3.2(%int_0) [concrete = constants.%int_0.6a9] +// CHECK:STDOUT: %.loc8_3: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9] +// CHECK:STDOUT: assign %y.var, %.loc8_3 +// CHECK:STDOUT: %.loc8_10: type = splice_block %i32 [concrete = constants.%i32] { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] +// CHECK:STDOUT: } +// CHECK:STDOUT: %y: ref %i32 = ref_binding y, %y.var +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %y.ref: ref %i32 = name_ref y, %y +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%y.ref) +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: = bound_method %y.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.424 +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method.loc8_3.3: = bound_method %y.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr: %ptr.235 = addr_of %y.var +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method.loc8_3.3(%addr) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- class.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %pattern_type.c48: type = pattern_type %C [concrete] +// CHECK:STDOUT: %C.F.type: type = fn_type @C.F [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %C.F: %C.F.type = struct_value () [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %complete_type.357: = complete_type_witness %empty_struct_type [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %type_where: type = facet_type > [concrete] +// CHECK:STDOUT: %facet_value: %type_where = facet_value %C, () [concrete] +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.eeb: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value) [concrete] +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.88a: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.eeb = struct_value () [concrete] +// CHECK:STDOUT: %ptr.019: type = ptr_type %C [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {} +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %C.F.decl: %C.F.type = fn_decl @C.F [concrete = constants.%C.F] { +// CHECK:STDOUT: %self.patt: %pattern_type.c48 = ref_binding_pattern self [concrete] +// CHECK:STDOUT: %self.param_patt: %pattern_type.c48 = ref_param_pattern %self.patt, call_param0 [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0 +// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C] +// CHECK:STDOUT: %self: ref %C = ref_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %complete_type: = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type.357] +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .F = %C.F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @C.F(%self.param: %C); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %c.patt: %pattern_type.c48 = ref_binding_pattern c [concrete] +// CHECK:STDOUT: %c.var_patt: %pattern_type.c48 = var_pattern %c.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %c.var: ref %C = var %c.var_patt +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %c: ref %C = ref_binding c, %c.var +// CHECK:STDOUT: %c.ref: ref %C = name_ref c, %c +// CHECK:STDOUT: %F.ref: %C.F.type = name_ref F, @C.%C.F.decl [concrete = constants.%C.F] +// CHECK:STDOUT: %C.F.bound: = bound_method %c.ref, %F.ref +// CHECK:STDOUT: %C.F.call: init %empty_tuple.type = call %C.F.bound(%c.ref) +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: = bound_method %c.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.88a +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method: = bound_method %c.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr: %ptr.019 = addr_of %c.var +// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon b/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon index fb17abfb65e3..1580f54d55e0 100644 --- a/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon +++ b/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon @@ -854,7 +854,7 @@ fn F[T:! Ptr](var t: T) -> T.(Ptr.Type) { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { // CHECK:STDOUT: %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete] // CHECK:STDOUT: %t.patt: @F.%pattern_type.loc9_20 (%pattern_type.e68) = ref_binding_pattern t [concrete] -// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_20 (%pattern_type.e68) = ref_param_pattern %t.patt, call_param0 [concrete] +// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_20 (%pattern_type.e68) = var_param_pattern %t.patt, call_param0 [concrete] // CHECK:STDOUT: %t.var_patt: @F.%pattern_type.loc9_20 (%pattern_type.e68) = var_pattern %t.param_patt [concrete] // CHECK:STDOUT: %return.patt: @F.%pattern_type.loc9_26 (%pattern_type.a60) = return_slot_pattern [concrete] // CHECK:STDOUT: %return.param_patt: @F.%pattern_type.loc9_26 (%pattern_type.a60) = out_param_pattern %return.patt, call_param1 [concrete] @@ -1055,7 +1055,7 @@ fn F[T:! Ptr](var t: T) -> T.(Ptr.Type) { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { // CHECK:STDOUT: %T.patt: %pattern_type.3d8 = symbolic_binding_pattern T, 0 [concrete] // CHECK:STDOUT: %t.patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = ref_binding_pattern t [concrete] -// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = ref_param_pattern %t.patt, call_param0 [concrete] +// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = var_param_pattern %t.patt, call_param0 [concrete] // CHECK:STDOUT: %t.var_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = var_pattern %t.param_patt [concrete] // CHECK:STDOUT: %return.patt: @F.%pattern_type.loc9_25 (%pattern_type.425) = return_slot_pattern [concrete] // CHECK:STDOUT: %return.param_patt: @F.%pattern_type.loc9_25 (%pattern_type.425) = out_param_pattern %return.patt, call_param1 [concrete] @@ -1262,7 +1262,7 @@ fn F[T:! Ptr](var t: T) -> T.(Ptr.Type) { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { // CHECK:STDOUT: %T.patt: %pattern_type.3d8 = symbolic_binding_pattern T, 0 [concrete] // CHECK:STDOUT: %t.patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = ref_binding_pattern t [concrete] -// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = ref_param_pattern %t.patt, call_param0 [concrete] +// CHECK:STDOUT: %t.param_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = var_param_pattern %t.patt, call_param0 [concrete] // CHECK:STDOUT: %t.var_patt: @F.%pattern_type.loc9_19 (%pattern_type.6f0) = var_pattern %t.param_patt [concrete] // CHECK:STDOUT: %return.patt: @F.%pattern_type.loc9_25 (%pattern_type.425) = return_slot_pattern [concrete] // CHECK:STDOUT: %return.param_patt: @F.%pattern_type.loc9_25 (%pattern_type.425) = out_param_pattern %return.patt, call_param1 [concrete] diff --git a/toolchain/check/testdata/let/ref.carbon b/toolchain/check/testdata/let/ref.carbon index ce2a58f2a041..938d5cf03f17 100644 --- a/toolchain/check/testdata/let/ref.carbon +++ b/toolchain/check/testdata/let/ref.carbon @@ -22,35 +22,6 @@ fn F(a: i32) { //@dump-sem-ir-end } -// --- param.carbon - -library "[[@TEST_NAME]]"; - -//@dump-sem-ir-begin -fn F(ref a: i32) { - a = 1; -} - -fn G() { - var x: i32 = 0; - F(x); -} -//@dump-sem-ir-end - -// --- self.carbon - -library "[[@TEST_NAME]]"; - -//@dump-sem-ir-begin -class C { - var a: i32; - - fn F[ref self: Self]() { - self.a = 1; - } -} -//@dump-sem-ir-end - // --- fail_ref_to_value.carbon library "[[@TEST_NAME]]"; @@ -123,180 +94,3 @@ fn F() { // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: --- param.carbon -// CHECK:STDOUT: -// CHECK:STDOUT: constants { -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] -// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] -// CHECK:STDOUT: %pattern_type.7ce: type = pattern_type %i32 [concrete] -// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] -// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] -// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete] -// CHECK:STDOUT: %ImplicitAs.type.d14: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] -// CHECK:STDOUT: %ImplicitAs.Convert.type.1b6: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%i32) [concrete] -// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51 = struct_value () [symbolic] -// CHECK:STDOUT: %ImplicitAs.impl_witness.bc9: = impl_witness imports.%ImplicitAs.impl_witness_table.132, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e = struct_value () [concrete] -// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.d14 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.bc9) [concrete] -// CHECK:STDOUT: %.322: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.265: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] -// CHECK:STDOUT: %bound_method.dfc: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] -// CHECK:STDOUT: %int_1.5d2: %i32 = int_value 1 [concrete] -// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] -// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] -// CHECK:STDOUT: %int_0.5c6: Core.IntLiteral = int_value 0 [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.0b3: = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete] -// CHECK:STDOUT: %bound_method.9be: = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] -// CHECK:STDOUT: %int_0.6a9: %i32 = int_value 0 [concrete] -// CHECK:STDOUT: %type_where: type = facet_type > [concrete] -// CHECK:STDOUT: %facet_value: %type_where = facet_value %i32, () [concrete] -// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.d4e: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value) [concrete] -// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.424: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.d4e = struct_value () [concrete] -// CHECK:STDOUT: %ptr.235: type = ptr_type %i32 [concrete] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: imports { -// CHECK:STDOUT: %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)] -// CHECK:STDOUT: %ImplicitAs.impl_witness_table.132 = impl_witness_table (%Core.import_ref.e24), @Core.IntLiteral.as.ImplicitAs.impl [concrete] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: file { -// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { -// CHECK:STDOUT: %a.patt: %pattern_type.7ce = ref_binding_pattern a [concrete] -// CHECK:STDOUT: %a.param_patt: %pattern_type.7ce = ref_param_pattern %a.patt, call_param0 [concrete] -// CHECK:STDOUT: } { -// CHECK:STDOUT: %a.param: ref %i32 = ref_param call_param0 -// CHECK:STDOUT: %.loc5: type = splice_block %i32 [concrete = constants.%i32] { -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] -// CHECK:STDOUT: } -// CHECK:STDOUT: %a: ref %i32 = ref_binding a, %a.param -// CHECK:STDOUT: } -// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: fn @F(%a.param: %i32) { -// CHECK:STDOUT: !entry: -// CHECK:STDOUT: %a.ref: ref %i32 = name_ref a, %a -// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8] -// CHECK:STDOUT: %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b] -// CHECK:STDOUT: %bound_method.loc6_5.1: = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.265] -// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] -// CHECK:STDOUT: %bound_method.loc6_5.2: = bound_method %int_1, %specific_fn [concrete = constants.%bound_method.dfc] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc6_5.2(%int_1) [concrete = constants.%int_1.5d2] -// CHECK:STDOUT: %.loc6: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_1.5d2] -// CHECK:STDOUT: assign %a.ref, %.loc6 -// CHECK:STDOUT: return -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: fn @G() { -// CHECK:STDOUT: !entry: -// CHECK:STDOUT: name_binding_decl { -// CHECK:STDOUT: %x.patt: %pattern_type.7ce = ref_binding_pattern x [concrete] -// CHECK:STDOUT: %x.var_patt: %pattern_type.7ce = var_pattern %x.patt [concrete] -// CHECK:STDOUT: } -// CHECK:STDOUT: %x.var: ref %i32 = var %x.var_patt -// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6] -// CHECK:STDOUT: %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b] -// CHECK:STDOUT: %bound_method.loc10_3.1: = bound_method %int_0, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.0b3] -// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] -// CHECK:STDOUT: %bound_method.loc10_3.2: = bound_method %int_0, %specific_fn [concrete = constants.%bound_method.9be] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc10_3.2(%int_0) [concrete = constants.%int_0.6a9] -// CHECK:STDOUT: %.loc10_3: init %i32 = converted %int_0, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9] -// CHECK:STDOUT: assign %x.var, %.loc10_3 -// CHECK:STDOUT: %.loc10_10: type = splice_block %i32 [concrete = constants.%i32] { -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] -// CHECK:STDOUT: } -// CHECK:STDOUT: %x: ref %i32 = ref_binding x, %x.var -// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] -// CHECK:STDOUT: %x.ref: ref %i32 = name_ref x, %x -// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%x.ref) -// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: = bound_method %x.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.424 -// CHECK:STDOUT: -// CHECK:STDOUT: %bound_method.loc10_3.3: = bound_method %x.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn -// CHECK:STDOUT: %addr: %ptr.235 = addr_of %x.var -// CHECK:STDOUT: %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method.loc10_3.3(%addr) -// CHECK:STDOUT: return -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: --- self.carbon -// CHECK:STDOUT: -// CHECK:STDOUT: constants { -// CHECK:STDOUT: %C: type = class_type @C [concrete] -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] -// CHECK:STDOUT: %C.elem: type = unbound_element_type %C, %i32 [concrete] -// CHECK:STDOUT: %pattern_type.c48: type = pattern_type %C [concrete] -// CHECK:STDOUT: %C.F.type: type = fn_type @C.F [concrete] -// CHECK:STDOUT: %C.F: %C.F.type = struct_value () [concrete] -// CHECK:STDOUT: %struct_type.a: type = struct_type {.a: %i32} [concrete] -// CHECK:STDOUT: %complete_type.fd7: = complete_type_witness %struct_type.a [concrete] -// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete] -// CHECK:STDOUT: %ImplicitAs.type.d14: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] -// CHECK:STDOUT: %ImplicitAs.Convert.type.1b6: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%i32) [concrete] -// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51 = struct_value () [symbolic] -// CHECK:STDOUT: %ImplicitAs.impl_witness.bc9: = impl_witness imports.%ImplicitAs.impl_witness_table.132, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.51e = struct_value () [concrete] -// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.d14 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.bc9) [concrete] -// CHECK:STDOUT: %.322: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] -// CHECK:STDOUT: %bound_method: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] -// CHECK:STDOUT: %int_1.5d2: %i32 = int_value 1 [concrete] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: imports { -// CHECK:STDOUT: %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)] -// CHECK:STDOUT: %ImplicitAs.impl_witness_table.132 = impl_witness_table (%Core.import_ref.e24), @Core.IntLiteral.as.ImplicitAs.impl [concrete] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: file { -// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {} -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: class @C { -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] -// CHECK:STDOUT: %.loc6: %C.elem = field_decl a, element0 [concrete] -// CHECK:STDOUT: %C.F.decl: %C.F.type = fn_decl @C.F [concrete = constants.%C.F] { -// CHECK:STDOUT: %self.patt: %pattern_type.c48 = ref_binding_pattern self [concrete] -// CHECK:STDOUT: %self.param_patt: %pattern_type.c48 = ref_param_pattern %self.patt, call_param0 [concrete] -// CHECK:STDOUT: } { -// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0 -// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C] -// CHECK:STDOUT: %self: ref %C = ref_binding self, %self.param -// CHECK:STDOUT: } -// CHECK:STDOUT: %complete_type: = complete_type_witness constants.%struct_type.a [concrete = constants.%complete_type.fd7] -// CHECK:STDOUT: complete_type_witness = %complete_type -// CHECK:STDOUT: -// CHECK:STDOUT: !members: -// CHECK:STDOUT: .Self = constants.%C -// CHECK:STDOUT: .a = %.loc6 -// CHECK:STDOUT: .F = %C.F.decl -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: fn @C.F(%self.param: %C) { -// CHECK:STDOUT: !entry: -// CHECK:STDOUT: %self.ref: ref %C = name_ref self, %self -// CHECK:STDOUT: %a.ref: %C.elem = name_ref a, @C.%.loc6 [concrete = @C.%.loc6] -// CHECK:STDOUT: %.loc9_9: ref %i32 = class_element_access %self.ref, element0 -// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8] -// CHECK:STDOUT: %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b] -// CHECK:STDOUT: %bound_method.loc9_12.1: = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] -// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] -// CHECK:STDOUT: %bound_method.loc9_12.2: = bound_method %int_1, %specific_fn [concrete = constants.%bound_method] -// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc9_12.2(%int_1) [concrete = constants.%int_1.5d2] -// CHECK:STDOUT: %.loc9_12: init %i32 = converted %int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_1.5d2] -// CHECK:STDOUT: assign %.loc9_9, %.loc9_12 -// CHECK:STDOUT: return -// CHECK:STDOUT: } -// CHECK:STDOUT: diff --git a/toolchain/check/testdata/var/var_pattern.carbon b/toolchain/check/testdata/var/var_pattern.carbon index 40febff80369..0ae2ecc35544 100644 --- a/toolchain/check/testdata/var/var_pattern.carbon +++ b/toolchain/check/testdata/var/var_pattern.carbon @@ -473,7 +473,7 @@ fn G() { // CHECK:STDOUT: %x.patt: %pattern_type.cb1 = value_binding_pattern x [concrete] // CHECK:STDOUT: %x.param_patt: %pattern_type.cb1 = value_param_pattern %x.patt, call_param0 [concrete] // CHECK:STDOUT: %y.patt: %pattern_type.cb1 = ref_binding_pattern y [concrete] -// CHECK:STDOUT: %y.param_patt: %pattern_type.cb1 = ref_param_pattern %y.patt, call_param1 [concrete] +// CHECK:STDOUT: %y.param_patt: %pattern_type.cb1 = var_param_pattern %y.patt, call_param1 [concrete] // CHECK:STDOUT: %y.var_patt: %pattern_type.cb1 = var_pattern %y.param_patt [concrete] // CHECK:STDOUT: } { // CHECK:STDOUT: %x.param: %empty_tuple.type = value_param call_param0 @@ -559,7 +559,7 @@ fn G() { // CHECK:STDOUT: %x.patt: %pattern_type = value_binding_pattern x [concrete] // CHECK:STDOUT: %x.param_patt: %pattern_type = value_param_pattern %x.patt, call_param0 [concrete] // CHECK:STDOUT: %y.patt: %pattern_type = ref_binding_pattern y [concrete] -// CHECK:STDOUT: %y.param_patt: %pattern_type = ref_param_pattern %y.patt, call_param1 [concrete] +// CHECK:STDOUT: %y.param_patt: %pattern_type = var_param_pattern %y.patt, call_param1 [concrete] // CHECK:STDOUT: %y.var_patt: %pattern_type = var_pattern %y.param_patt [concrete] // CHECK:STDOUT: } { // CHECK:STDOUT: %x.param: %empty_tuple.type = value_param call_param0 @@ -607,7 +607,7 @@ fn G() { // CHECK:STDOUT: %x.patt: %pattern_type = value_binding_pattern x [concrete] // CHECK:STDOUT: %x.param_patt: %pattern_type = value_param_pattern %x.patt, call_param0 [concrete] // CHECK:STDOUT: %y.patt: %pattern_type = ref_binding_pattern y [concrete] -// CHECK:STDOUT: %y.param_patt: %pattern_type = ref_param_pattern %y.patt, call_param1 [concrete] +// CHECK:STDOUT: %y.param_patt: %pattern_type = var_param_pattern %y.patt, call_param1 [concrete] // CHECK:STDOUT: %y.var_patt: %pattern_type = var_pattern %y.param_patt [concrete] // CHECK:STDOUT: } { // CHECK:STDOUT: %x.param: %empty_tuple.type = value_param call_param0 @@ -863,7 +863,7 @@ fn G() { // CHECK:STDOUT: class @C { // CHECK:STDOUT: %C.F.decl: %C.F.type = fn_decl @C.F [concrete = constants.%C.F] { // CHECK:STDOUT: %self.patt: %pattern_type = ref_binding_pattern self [concrete] -// CHECK:STDOUT: %self.param_patt: %pattern_type = ref_param_pattern %self.patt, call_param0 [concrete] +// CHECK:STDOUT: %self.param_patt: %pattern_type = var_param_pattern %self.patt, call_param0 [concrete] // CHECK:STDOUT: %self.var_patt: %pattern_type = var_pattern %self.param_patt [concrete] // CHECK:STDOUT: } { // CHECK:STDOUT: %self.param: ref %C = ref_param call_param0 diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index db7fb3719c90..cc5a6e95c37a 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -298,8 +298,7 @@ static auto BuildPatternRef(Context& context, auto pattern_ref_id = SemIR::InstId::None; if (auto value_param = pattern.TryAs(); value_param.has_value() && - (value_param->kind == SemIR::ValueParamPattern::Kind || - value_param->kind == SemIR::RefParamPattern::Kind)) { + value_param->kind != SemIR::OutParamPattern::Kind) { pattern_ref_id = arg_ids[value_param->index.index]; } else { if (pattern_id != SemIR::ErrorInst::InstId) { diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 95ee7696f1ad..b780dd679be7 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -118,6 +118,7 @@ CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresWhitespace) CARBON_DIAGNOSTIC_KIND(UnexpectedTokenAfterListElement) CARBON_DIAGNOSTIC_KIND(UnrecognizedDecl) CARBON_DIAGNOSTIC_KIND(UnexpectedTokenInCompoundMemberAccess) +CARBON_DIAGNOSTIC_KIND(UnexpectedRef) // Match diagnostics. CARBON_DIAGNOSTIC_KIND(ExpectedMatchCaseArrow) @@ -255,6 +256,9 @@ CARBON_DIAGNOSTIC_KIND(InCallToEntity) CARBON_DIAGNOSTIC_KIND(InCallToFunction) CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam) CARBON_DIAGNOSTIC_KIND(MissingObjectInMethodCall) +CARBON_DIAGNOSTIC_KIND(RefParamNoRefTag) +CARBON_DIAGNOSTIC_KIND(RefTagNoRefParam) +CARBON_DIAGNOSTIC_KIND(RefTagNotDurableRef) CARBON_DIAGNOSTIC_KIND(SelfParameterNotAllowed) CARBON_DIAGNOSTIC_KIND(ValueForRefParam) diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 50b7f57937d8..92473c8ac286 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -357,7 +357,8 @@ auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function, } // TODO: Use a more general mechanism to determine if the binding is a // reference binding. - if (param_pattern_info->inst.kind == SemIR::RefParamPattern::Kind) { + if (param_pattern_info->inst.kind == SemIR::RefParamPattern::Kind || + param_pattern_info->inst.kind == SemIR::VarParamPattern::Kind) { param_types.push_back( llvm::PointerType::get(llvm_context(), /*AddressSpace=*/0)); param_inst_ids.push_back(param_pattern_id); diff --git a/toolchain/lower/testdata/function/call/ref_param.carbon b/toolchain/lower/testdata/function/call/ref_param.carbon index 7d67489e4254..e107f0ec15dd 100644 --- a/toolchain/lower/testdata/function/call/ref_param.carbon +++ b/toolchain/lower/testdata/function/call/ref_param.carbon @@ -14,7 +14,7 @@ fn DoNothing(ref a: i32) {} fn Main() { var a: i32 = 0; - DoNothing(a); + DoNothing(ref a); } // CHECK:STDOUT: ; ModuleID = 'ref_param.carbon' diff --git a/toolchain/parse/handle_brace_expr.cpp b/toolchain/parse/handle_brace_expr.cpp index 205b74ca893a..f44114a3fb8d 100644 --- a/toolchain/parse/handle_brace_expr.cpp +++ b/toolchain/parse/handle_brace_expr.cpp @@ -133,6 +133,11 @@ static auto HandleBraceExprParamAfterDesignator(Context& context, // that one has a `:` separator and the other has an `=` separator. state.token = context.Consume(); context.PushState(state, param_finish_kind); + if (param_finish_kind == StateKind::BraceExprParamFinishAsValue && + context.PositionIs(Lex::TokenKind::Ref)) { + context.PushState(StateKind::RefTagFinishAsRegular); + context.ConsumeChecked(Lex::TokenKind::Ref); + } context.PushState(StateKind::Expr); } diff --git a/toolchain/parse/handle_call_expr.cpp b/toolchain/parse/handle_call_expr.cpp deleted file mode 100644 index be06035fc500..000000000000 --- a/toolchain/parse/handle_call_expr.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// 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 - -#include "toolchain/parse/context.h" -#include "toolchain/parse/handle.h" - -namespace Carbon::Parse { - -auto HandleCallExpr(Context& context) -> void { - auto state = context.PopState(); - context.PushState(state, StateKind::CallExprFinish); - - context.AddNode(NodeKind::CallExprStart, context.Consume(), state.has_error); - if (!context.PositionIs(Lex::TokenKind::CloseParen)) { - context.PushState(StateKind::CallExprParamFinish); - context.PushState(StateKind::Expr); - } -} - -auto HandleCallExprParamFinish(Context& context) -> void { - auto state = context.PopState(); - - if (state.has_error) { - context.ReturnErrorOnState(); - } - - if (context.ConsumeListToken(NodeKind::CallExprComma, - Lex::TokenKind::CloseParen, state.has_error) == - Context::ListTokenKind::Comma) { - context.PushState(StateKind::CallExprParamFinish); - context.PushState(StateKind::Expr); - } -} - -auto HandleCallExprFinish(Context& context) -> void { - auto state = context.PopState(); - - context.AddNode(NodeKind::CallExpr, context.Consume(), state.has_error); -} - -} // namespace Carbon::Parse diff --git a/toolchain/parse/handle_paren_expr.cpp b/toolchain/parse/handle_paren_expr.cpp index dfa5f8e6e196..38ed89643251 100644 --- a/toolchain/parse/handle_paren_expr.cpp +++ b/toolchain/parse/handle_paren_expr.cpp @@ -43,6 +43,34 @@ auto HandleOnlyParenExprFinish(Context& context) -> void { FinishParenExpr(context, state); } +static auto HandleRefTagFinish(Context& context, StateKind state_kind) -> void { + auto state = context.PopState(); + if (state_kind == StateKind::RefTagFinishAsAfterOpenParen && + !context.PositionIs(Lex::TokenKind::Comma)) { + CARBON_DIAGNOSTIC(UnexpectedRef, Error, + "found `ref` in unexpected position"); + context.emitter().Emit(state.token, UnexpectedRef); + } + context.AddNode(NodeKind::RefTag, state.token, state.has_error); +} + +auto HandleRefTagFinishAsRegular(Context& context) -> void { + HandleRefTagFinish(context, StateKind::RefTagFinishAsRegular); +} + +auto HandleRefTagFinishAsAfterOpenParen(Context& context) -> void { + HandleRefTagFinish(context, StateKind::RefTagFinishAsAfterOpenParen); +} + +static auto StartTupleLiteralElement(Context& context) -> void { + context.PushState(StateKind::TupleLiteralElementFinish); + if (context.PositionIs(Lex::TokenKind::Ref)) { + context.PushState(StateKind::RefTagFinishAsRegular); + context.ConsumeChecked(Lex::TokenKind::Ref); + } + context.PushState(StateKind::Expr); +} + auto HandleParenExpr(Context& context) -> void { auto state = context.PopState(); @@ -56,6 +84,10 @@ auto HandleParenExpr(Context& context) -> void { } else { context.PushState(state, StateKind::ParenExprFinish); context.PushState(StateKind::ExprAfterOpenParenFinish); + if (context.PositionIs(Lex::TokenKind::Ref)) { + context.PushState(StateKind::RefTagFinishAsAfterOpenParen); + context.ConsumeChecked(Lex::TokenKind::Ref); + } context.PushState(StateKind::Expr); } } @@ -78,25 +110,26 @@ auto HandleExprAfterOpenParenFinish(Context& context) -> void { // If the comma is not immediately followed by a close paren, push handlers // for the next tuple element. if (list_token_kind != Context::ListTokenKind::CommaClose) { - context.PushState(state, StateKind::TupleLiteralElementFinish); - context.PushState(StateKind::Expr); + StartTupleLiteralElement(context); } } auto HandleTupleLiteralElementFinish(Context& context) -> void { auto state = context.PopState(); + if (state.has_error) { + context.ReturnErrorOnState(); + } + if (context.ConsumeListToken(NodeKind::TupleLiteralComma, Lex::TokenKind::CloseParen, state.has_error) == Context::ListTokenKind::Comma) { - context.PushState(state); - context.PushState(StateKind::Expr); + StartTupleLiteralElement(context); } } auto HandleParenExprFinish(Context& context) -> void { auto state = context.PopState(); - context.ReplacePlaceholderNode(state.subtree_start, NodeKind::ParenExprStart, state.token); FinishParenExpr(context, state); @@ -110,4 +143,20 @@ auto HandleTupleLiteralFinish(Context& context) -> void { context.AddNode(NodeKind::TupleLiteral, context.Consume(), state.has_error); } +auto HandleCallExpr(Context& context) -> void { + auto state = context.PopState(); + context.PushState(state, StateKind::CallExprFinish); + + context.AddNode(NodeKind::CallExprStart, context.Consume(), state.has_error); + if (!context.PositionIs(Lex::TokenKind::CloseParen)) { + StartTupleLiteralElement(context); + } +} + +auto HandleCallExprFinish(Context& context) -> void { + auto state = context.PopState(); + + context.AddNode(NodeKind::CallExpr, context.Consume(), state.has_error); +} + } // namespace Carbon::Parse diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 5d98c973c194..44856e118671 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -234,7 +234,6 @@ CARBON_PARSE_NODE_KIND(TupleLiteralComma) CARBON_PARSE_NODE_KIND(TupleLiteral) CARBON_PARSE_NODE_KIND(CallExprStart) -CARBON_PARSE_NODE_KIND(CallExprComma) CARBON_PARSE_NODE_KIND(CallExpr) CARBON_PARSE_NODE_KIND(MemberAccessExpr) @@ -243,6 +242,8 @@ CARBON_PARSE_NODE_KIND(PointerMemberAccessExpr) CARBON_PARSE_NODE_KIND(IntLiteral) +CARBON_PARSE_NODE_KIND(RefTag) + CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralFalse, False) CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralTrue, True) CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(CharLiteral, CharLiteral) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index e64efe3f8785..dd8002506eb5 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -125,6 +125,12 @@ CARBON_PARSE_STATE_VARIANTS3(BraceExprParam, Type, Value, Unknown) // Handles a brace expression parameter after the initial designator. This // should be at a `:` or `=`, depending on whether it's a type or value literal. // +// { .foo = ref bar ... } +// ^~~~~ +// 1. Expr +// 2. RefTagFinishAsRegular +// 3. BraceExprParamFinishAsValue +// // { .foo = bar ... } // ^ // 1. Expr @@ -167,31 +173,35 @@ CARBON_PARSE_STATE_VARIANTS3(BraceExprParamFinish, Type, Value, Unknown) // (state done) CARBON_PARSE_STATE_VARIANTS3(BraceExprFinish, Type, Value, Unknown) -// Handles a call expression `(...)`. +// Handles the end of an argument expression tagged with `ref`. The +// `AfterOpenParen` variant diagnoses if the expression is not followed by `,`. +// +// ref ... +// ^ +// (state done) +CARBON_PARSE_STATE_VARIANTS2(RefTagFinish, Regular, AfterOpenParen) + +// Handles a call expression `(...)`, including the initial `ref`, if any. // // F() // ^ // 1. CallExprFinish // +// F(ref ... +// ^~~~ +// +// 1. Expr +// 2. RefTagFinishAsRegular +// 2. TupleLiteralElementFinish +// 3. CallExprFinish +// // F( ... // ^ // 1. Expr -// 2. CallExprParamFinish +// 2. TupleLiteralElementFinish // 3. CallExprFinish CARBON_PARSE_STATE(CallExpr) -// Handles the `,` or `)` after a call parameter. -// -// F(a, ...) -// ^ -// 1. Expr -// 2. CallExprParamFinish -// -// F(a ) -// ^ -// (state done) -CARBON_PARSE_STATE(CallExprParamFinish) - // Handles finishing the call expression. // // F(a, b) @@ -924,17 +934,29 @@ CARBON_PARSE_STATE(OnlyParenExpr) // ^ CARBON_PARSE_STATE(OnlyParenExprFinish) -// Handles the `(` of a parenthesized single expression +// Handles the `(` of an expression that's presumed to be a parenthesized +// single expression, but may later be reclassified as a tuple literal. +// This also handles a `ref` following the `(`, if present. That's only +// valid in tuple literals, but doesn't cause the expression to be a tuple +// literal. // // ( ) // ^ // 1. TupleLiteralFinish // +// (ref ... ) +// ^~~~ +// +// 1. Expr +// 2. RefTagFinishAsAfterOpenParen +// 3. ExprAfterOpenParenFinish +// 4. ParenExprFinish (SPECIAL: may be replaced) +// // ( ... ) // ^ // 1. Expr // 2. ExprAfterOpenParenFinish -// 3. ParenExprFinish (SPECIAL: may be replaced) +// 3. ParenExprFinish (SPECIAL: may be replaced) CARBON_PARSE_STATE(ParenExpr) // Handles the `)` of a tuple literal. @@ -951,6 +973,13 @@ CARBON_PARSE_STATE(TupleLiteralFinish) // (state done) // SPECIAL: parent becomes TupleLiteralFinish // +// ( ... , ref ... ) +// ^~~~~ +// 1. Expr +// 2. RefTagFinishAsRegular +// 3. TupleLiteralElementFinish +// SPECIAL: parent becomes TupleLiteralFinish +// // ( ... , ... ) // ^ // 1. Expr @@ -969,6 +998,12 @@ CARBON_PARSE_STATE(ExprAfterOpenParenFinish) // ^ // (state done) // +// ( ... , ref ... ) +// ^~~~~ +// 1. Expr +// 2. RefTagFinishAsRegular +// 3. TupleLiteralElementFinish +// // ( ... , ... ) // ^ // 1. Expr diff --git a/toolchain/parse/testdata/basics/function_call.carbon b/toolchain/parse/testdata/basics/function_call.carbon index f04bbe388fbd..d7c033636c85 100644 --- a/toolchain/parse/testdata/basics/function_call.carbon +++ b/toolchain/parse/testdata/basics/function_call.carbon @@ -30,7 +30,7 @@ fn F() { // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, // CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'd'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, -// CHECK:STDOUT: {kind: 'CallExprComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, // CHECK:STDOUT: {kind: 'ParenExprStart', text: '('}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'e'}, // CHECK:STDOUT: {kind: 'ParenExpr', text: ')', subtree_size: 3}, diff --git a/toolchain/parse/testdata/function/call.carbon b/toolchain/parse/testdata/function/call.carbon new file mode 100644 index 000000000000..e04fb2e85d59 --- /dev/null +++ b/toolchain/parse/testdata/function/call.carbon @@ -0,0 +1,201 @@ +// 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 +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/function/call.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/call.carbon + +// --- basic.carbon + +fn F() { + a.b.f(c.d, (e)).g(); +} + +// --- ref_args.carbon + +fn F() { + G(ref x); + G(ref x, y); + G(x, ref y); +} + +// --- nested_ref.carbon + +fn F() { + G(x, (ref x,)); + G(x, (ref y, z)); + G(x, (y, ref z)); + + G(x, {.y = ref y, .z = z}); +} + +// --- fail_ref_out_of_place.carbon + +fn F() { + (ref x,); + G(ref x); + // CHECK:STDERR: fail_ref_out_of_place.carbon:[[@LINE+4]]:4: error: found `ref` in unexpected position [UnexpectedRef] + // CHECK:STDERR: (ref x); + // CHECK:STDERR: ^~~ + // CHECK:STDERR: + (ref x); +} + +// CHECK:STDOUT: - filename: basic.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'b'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'f'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'd'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'ParenExprStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'e'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'g'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 16}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 17}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 19}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 25}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: ref_args.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 28}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: nested_ref.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteral', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'z'}, +// CHECK:STDOUT: {kind: 'TupleLiteral', text: ')', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'z'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'TupleLiteral', text: ')', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'StructLiteralStart', text: '{'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'y'}, +// CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'StructLiteralField', text: '=', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'StructLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeParams', text: 'z'}, +// CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'z'}, +// CHECK:STDOUT: {kind: 'StructLiteralField', text: '=', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 17}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 59}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_ref_out_of_place.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'TupleLiteralStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteral', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'G'}, +// CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'ParenExprStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'RefTag', text: 'ref', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 23}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/definition.carbon b/toolchain/parse/testdata/function/definition.carbon index 7eb38f07f96b..2b4812b3d334 100644 --- a/toolchain/parse/testdata/function/definition.carbon +++ b/toolchain/parse/testdata/function/definition.carbon @@ -107,7 +107,7 @@ fn TestRecoveryFromSpuriousEquals(); // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'foo'}, // CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'baz'}, -// CHECK:STDOUT: {kind: 'CallExprComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'bar'}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'baz'}, // CHECK:STDOUT: {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3}, diff --git a/toolchain/parse/testdata/operators/fail_postfix_space_before_comma.carbon b/toolchain/parse/testdata/operators/fail_postfix_space_before_comma.carbon index a1206d3bfd92..45b373429220 100644 --- a/toolchain/parse/testdata/operators/fail_postfix_space_before_comma.carbon +++ b/toolchain/parse/testdata/operators/fail_postfix_space_before_comma.carbon @@ -27,7 +27,7 @@ var n: i8 = F(i8 *, 0); // CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, // CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i8'}, // CHECK:STDOUT: {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'CallExprComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '0'}, // CHECK:STDOUT: {kind: 'CallExpr', text: ')', subtree_size: 7}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 14}, diff --git a/toolchain/parse/testdata/operators/fixity_in_call.carbon b/toolchain/parse/testdata/operators/fixity_in_call.carbon index f28709d4be2e..d2c936c0189e 100644 --- a/toolchain/parse/testdata/operators/fixity_in_call.carbon +++ b/toolchain/parse/testdata/operators/fixity_in_call.carbon @@ -24,7 +24,7 @@ fn F() { // CHECK:STDOUT: {kind: 'CallExprStart', text: '(', subtree_size: 2}, // CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, // CHECK:STDOUT: {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'CallExprComma', text: ','}, +// CHECK:STDOUT: {kind: 'TupleLiteralComma', text: ','}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'n'}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'n'}, // CHECK:STDOUT: {kind: 'InfixOperatorStar', text: '*', subtree_size: 3}, diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index cd5a18102012..f30ed22d7f17 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -996,18 +996,25 @@ struct CallExprStart { Lex::OpenParenTokenIndex token; }; -using CallExprComma = LeafNode; - // A call expression: `F(a, b, c)`. struct CallExpr { static constexpr auto Kind = NodeKind::CallExpr.Define( {.category = NodeCategory::Expr, .bracketed_by = CallExprStart::Kind}); CallExprStartId start; - CommaSeparatedList arguments; + CommaSeparatedList arguments; Lex::CloseParenTokenIndex token; }; +// A callsite `ref` tag: `F(ref x)` or `F({.x = ref x}) +struct RefTag { + static constexpr auto Kind = NodeKind::RefTag.Define( + {.category = NodeCategory::Expr, .child_count = 1}); + + Lex::RefTokenIndex token; + AnyExprId tagged_expr; +}; + // A member access expression: `a.b` or `a.(b)`. struct MemberAccessExpr { static constexpr auto Kind = NodeKind::MemberAccessExpr.Define( diff --git a/toolchain/sem_ir/expr_info.cpp b/toolchain/sem_ir/expr_info.cpp index 4fcc25fb930a..a1019b98e4bd 100644 --- a/toolchain/sem_ir/expr_info.cpp +++ b/toolchain/sem_ir/expr_info.cpp @@ -47,6 +47,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory { case TuplePattern::Kind: case ValueBindingPattern::Kind: case ValueParamPattern::Kind: + case VarParamPattern::Kind: case VarPattern::Kind: return ExprCategory::NotExpr; diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index b7a74b29938e..dc97308bba22 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -157,6 +157,43 @@ auto File::OutputYaml(bool include_singletons) const -> Yaml::OutputMapping { }); } +auto File::CollectRefTagsNeeded() const -> Set { + CARBON_CHECK(!has_errors_); + Set ref_tags_needed; + for (auto [id, inst] : insts_.enumerate()) { + if (inst.kind() != SemIR::InstKind::Call) { + continue; + } + auto call_inst = inst.As(); + auto callee = SemIR::GetCallee(*this, call_inst.callee_id); + CARBON_KIND_SWITCH(callee) { + case CARBON_KIND(SemIR::CalleeError _): + break; + case CARBON_KIND(SemIR::CalleeNonFunction _): + break; + case CARBON_KIND(SemIR::CalleeCppOverloadSet _): { + // TODO: Perform validation here once we model C++ ref parameters as + // Carbon ref parameters. + break; + } + case CARBON_KIND(SemIR::CalleeFunction fn): { + auto function = functions_.Get(fn.function_id); + auto args = inst_blocks_.GetOrEmpty(call_inst.args_id); + for (auto param_id : llvm::concat( + inst_blocks_.GetOrEmpty(function.implicit_param_patterns_id), + inst_blocks_.GetOrEmpty(function.param_patterns_id))) { + if (auto ref_param_pattern = + insts_.TryGetAs(param_id)) { + ref_tags_needed.Insert(args[ref_param_pattern->index.index]); + } + } + break; + } + } + } + return ref_tags_needed; +} + auto File::CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const -> void { mem_usage.Collect(MemUsage::ConcatLabel(label, "allocator_"), allocator_); diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index fc61de5aa9c0..80ad2548c7c3 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -94,6 +94,12 @@ class File : public Printable { } auto OutputYaml(bool include_singletons) const -> Yaml::OutputMapping; + // Returns the set of all insts corresponding to expressions that are used + // in positions where a `ref` tag is needed. Should only be called if + // has_errors is false. This is intended for validation purposes, and should + // only be called if !NDEBUG, because it walks the entire IR. + auto CollectRefTagsNeeded() const -> Set; + // Collects memory usage of members. auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const -> void; diff --git a/toolchain/sem_ir/inst_categories.h b/toolchain/sem_ir/inst_categories.h index 78daa1312b5a..0c467ca8b6fa 100644 --- a/toolchain/sem_ir/inst_categories.h +++ b/toolchain/sem_ir/inst_categories.h @@ -157,11 +157,10 @@ struct AnyParam { }; // A pattern that represents a `Call` parameter. It delegates to subpattern_id -// in pattern matching. The sub-kinds differ only in the expression category -// of the corresponding parameter inst. +// in pattern matching. struct AnyParamPattern { - using CategoryInfo = - CategoryOf; + using CategoryInfo = CategoryOf; InstKind kind; diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 873b9cc98193..b356cfe428cf 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -153,6 +153,7 @@ CARBON_SEM_IR_INST_KIND(ValueBindingPattern) CARBON_SEM_IR_INST_KIND(ValueOfInitializer) CARBON_SEM_IR_INST_KIND(ValueParam) CARBON_SEM_IR_INST_KIND(ValueParamPattern) +CARBON_SEM_IR_INST_KIND(VarParamPattern) CARBON_SEM_IR_INST_KIND(VarPattern) CARBON_SEM_IR_INST_KIND(VarStorage) CARBON_SEM_IR_INST_KIND(VtableDecl) diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index 9dcfaa339659..496992bfcc8d 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -1153,7 +1153,8 @@ auto InstNamer::NamingContext::NameInst() -> void { } case OutParamPattern::Kind: case RefParamPattern::Kind: - case ValueParamPattern::Kind: { + case ValueParamPattern::Kind: + case VarParamPattern::Kind: { AddInstNameId(GetPrettyNameFromPatternId(sem_ir(), inst_id_), ".param_patt"); return; diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 3b87a5b559e9..a0a0ec0f61ca 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -1296,7 +1296,8 @@ struct RefBindingPattern { EntityNameId entity_name_id; }; -// A by-reference `Call` parameter. See AnyParam for member documentation. +// A by-reference `Call` parameter. See AnyParam for member documentation. Note +// that this may correspond to either a RefParamPattern or a VarParamPattern. struct RefParam { // TODO: Make Parse::NodeId more specific. static constexpr auto Kind = InstKind::RefParam.Define( @@ -1307,7 +1308,7 @@ struct RefParam { NameId pretty_name_id; }; -// A pattern that represents a by-reference `Call` parameter. See +// A pattern that represents a `ref`-qualified `Call` parameter. See // `AnyParamPattern` for member documentation. struct RefParamPattern { // TODO: Make Parse::NodeId more specific. @@ -1912,6 +1913,21 @@ struct ValueParamPattern { CallParamIndex index; }; +// A pattern that represents a `Call` parameter corresponding to a `var` +// pattern. See `AnyParamPattern` for member documentation. Note that there is +// no `VarParam` -- a `VarParamPattern` corresponds to a `RefParam`. +struct VarParamPattern { + static constexpr auto Kind = + InstKind::VarParamPattern.Define( + {.ir_name = "var_param_pattern", + .constant_kind = InstConstantKind::AlwaysUnique, + .is_lowered = false}); + + TypeId type_id; + InstId subpattern_id; + CallParamIndex index; +}; + // A `var` pattern. struct VarPattern { static constexpr auto Kind =