Sfoglia il codice sorgente

Parse `ref` as operator (#6539)

This is the first step of implementing the guidance in #6342.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Geoff Romer 4 mesi fa
parent
commit
e940cb72b6

+ 4 - 1
docs/design/expressions/README.md

@@ -145,6 +145,8 @@ graph BT
 
     logicalExpression((" "))
 
+    ref["ref x"]
+
     if>"if x then y else z"]
     click if "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/if.md"
 
@@ -187,7 +189,8 @@ graph BT
 
     and & or --> logicalOperand
     logicalExpression --> as & where & and & or
-    if & expressionStatement --> logicalExpression
+    ref & expressionStatement --> logicalExpression
+    if ---> ref
     insideParens & assignment --> if
 ```
 

+ 0 - 14
toolchain/check/handle_call_expr.cpp

@@ -33,18 +33,4 @@ 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<Parse::NodeCategory::Expr>();
-
-  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

+ 15 - 0
toolchain/check/handle_operator.cpp

@@ -357,6 +357,21 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorPlusPlusId node_id)
   return HandleUnaryOperator(context, node_id, CoreIdentifier::Inc);
 }
 
+auto HandleParseNode(Context& context, Parse::PrefixOperatorRefId node_id)
+    -> bool {
+  auto expr_id = context.node_stack().Peek<Parse::NodeCategory::Expr>();
+
+  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;
+}
+
 auto HandleParseNode(Context& context, Parse::PrefixOperatorStarId node_id)
     -> bool {
   auto base_id = context.node_stack().PopExpr();

+ 43 - 0
toolchain/check/testdata/operators/builtin/ref.carbon

@@ -0,0 +1,43 @@
+// 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/destroy.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/operators/builtin/ref.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/operators/builtin/ref.carbon
+
+// --- fail_discarded_ref.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  var x: () = ();
+  // CHECK:STDERR: fail_discarded_ref.carbon:[[@LINE+4]]:7: error: `ref` tag is not an argument to a `ref` parameter [RefTagNoRefParam]
+  // CHECK:STDERR:   ref x;
+  // CHECK:STDERR:       ^
+  // CHECK:STDERR:
+  ref x;
+}
+
+// --- fail_ref_out_of_place.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  var x: () = ();
+  // CHECK:STDERR: fail_ref_out_of_place.carbon:[[@LINE+4]]:19: error: `ref` tag is not an argument to a `ref` parameter [RefTagNoRefParam]
+  // CHECK:STDERR:   let y: () = ref x;
+  // CHECK:STDERR:                   ^
+  // CHECK:STDERR:
+  let y: () = ref x;
+  // CHECK:STDERR: fail_ref_out_of_place.carbon:[[@LINE+4]]:20: error: `ref` tag is not an argument to a `ref` parameter [RefTagNoRefParam]
+  // CHECK:STDERR:   let z: () = (ref x);
+  // CHECK:STDERR:                    ^
+  // CHECK:STDERR:
+  let z: () = (ref x);
+}

+ 0 - 1
toolchain/diagnostics/diagnostic_kind.def

@@ -119,7 +119,6 @@ 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)

+ 0 - 5
toolchain/parse/handle_brace_expr.cpp

@@ -133,11 +133,6 @@ 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);
 }
 

+ 6 - 35
toolchain/parse/handle_paren_expr.cpp

@@ -43,34 +43,6 @@ 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();
 
@@ -84,10 +56,6 @@ 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);
   }
 }
@@ -110,7 +78,8 @@ 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) {
-    StartTupleLiteralElement(context);
+    context.PushState(StateKind::TupleLiteralElementFinish);
+    context.PushState(StateKind::Expr);
   }
 }
 
@@ -124,7 +93,8 @@ auto HandleTupleLiteralElementFinish(Context& context) -> void {
   if (context.ConsumeListToken(NodeKind::TupleLiteralComma,
                                Lex::TokenKind::CloseParen, state.has_error) ==
       Context::ListTokenKind::Comma) {
-    StartTupleLiteralElement(context);
+    context.PushState(StateKind::TupleLiteralElementFinish);
+    context.PushState(StateKind::Expr);
   }
 }
 
@@ -149,7 +119,8 @@ auto HandleCallExpr(Context& context) -> void {
 
   context.AddNode(NodeKind::CallExprStart, context.Consume(), state.has_error);
   if (!context.PositionIs(Lex::TokenKind::CloseParen)) {
-    StartTupleLiteralElement(context);
+    context.PushState(StateKind::TupleLiteralElementFinish);
+    context.PushState(StateKind::Expr);
   }
 }
 

+ 1 - 2
toolchain/parse/node_kind.def

@@ -242,8 +242,6 @@ 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)
@@ -268,6 +266,7 @@ CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Minus)
 CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(MinusMinus)
 CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Partial)
 CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(PlusPlus)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Ref)
 CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Star)
 
 CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Amp)

+ 5 - 1
toolchain/parse/precedence.cpp

@@ -30,7 +30,8 @@ struct PrecedenceGroup::OperatorPriorityTable {
         {Additive, Modulo, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift},
         {Relational, Where});
     MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr});
-    MarkHigherThan({As, LogicalAnd, LogicalOr, Where}, {If});
+    MarkHigherThan({As, LogicalAnd, LogicalOr, Where}, {Ref});
+    MarkHigherThan({Ref}, {If});
     MarkHigherThan({If}, {Assignment});
     MarkHigherThan({Assignment, IncrementDecrement}, {Lowest});
 
@@ -166,6 +167,9 @@ auto PrecedenceGroup::ForLeading(Lex::TokenKind kind)
     case Lex::TokenKind::If:
       return PrecedenceGroup(If);
 
+    case Lex::TokenKind::Ref:
+      return PrecedenceGroup(Ref);
+
     case Lex::TokenKind::Const:
     case Lex::TokenKind::Partial:
       return PrecedenceGroup(TypePrefix);

+ 2 - 0
toolchain/parse/precedence.h

@@ -123,6 +123,8 @@ class PrecedenceGroup {
     LogicalOr,
     // Conditional.
     If,
+    // `ref`
+    Ref,
     // Assignment.
     Assignment,
     // Sentinel representing a context in which any operator can appear.

+ 4 - 47
toolchain/parse/state.def

@@ -125,12 +125,6 @@ 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
@@ -173,28 +167,12 @@ CARBON_PARSE_STATE_VARIANTS3(BraceExprParamFinish, Type, Value, Unknown)
 //   (state done)
 CARBON_PARSE_STATE_VARIANTS3(BraceExprFinish, Type, Value, Unknown)
 
-// 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.
+// Handles a call expression `(...)`.
 //
 // F()
 //  ^
 //   1. CallExprFinish
 //
-// F(ref ...
-//  ^~~~
-//
-// 1. Expr
-// 2. RefTagFinishAsRegular
-// 2. TupleLiteralElementFinish
-// 3. CallExprFinish
-//
 // F( ...
 //  ^
 //   1. Expr
@@ -936,22 +914,11 @@ CARBON_PARSE_STATE(OnlyParenExprFinish)
 
 // 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
@@ -973,13 +940,6 @@ CARBON_PARSE_STATE(TupleLiteralFinish)
 //   (state done)
 //   SPECIAL: parent becomes TupleLiteralFinish
 //
-// ( ... , ref ... )
-//       ^~~~~
-//   1. Expr
-//   2. RefTagFinishAsRegular
-//   3. TupleLiteralElementFinish
-//   SPECIAL: parent becomes TupleLiteralFinish
-//
 // ( ... , ... )
 //       ^
 //   1. Expr
@@ -998,12 +958,6 @@ CARBON_PARSE_STATE(ExprAfterOpenParenFinish)
 //       ^
 //   (state done)
 //
-// ( ... , ref ... )
-//       ^~~~~
-//   1. Expr
-//   2. RefTagFinishAsRegular
-//   3. TupleLiteralElementFinish
-//
 // ( ... , ... )
 //       ^
 //   1. Expr
@@ -1044,6 +998,9 @@ CARBON_PARSE_STATE(Pattern)
 // Handles the initial part of a binding pattern, enqueuing type expression
 // processing.
 //
+// TODO: treat `ref` as a unary pattern operator in order to avoid ambiguity
+// with `ref` in expression patterns. See issue #6342.
+//
 // [ref] name: ...
 // ^~~~~
 // [ref] self: ...

+ 7 - 47
toolchain/parse/testdata/function/call.carbon

@@ -32,18 +32,6 @@ fn F() {
   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: ''},
@@ -85,13 +73,13 @@ fn F() {
 // 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: 'PrefixOperatorRef', 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: 'PrefixOperatorRef', text: 'ref', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'TupleLiteralComma', text: ','},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'y'},
 // CHECK:STDOUT:         {kind: 'CallExpr', text: ')', subtree_size: 7},
@@ -101,7 +89,7 @@ fn F() {
 // 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: 'PrefixOperatorRef', 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},
@@ -121,7 +109,7 @@ fn F() {
 // 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: 'PrefixOperatorRef', 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},
@@ -132,7 +120,7 @@ fn F() {
 // 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: 'PrefixOperatorRef', text: 'ref', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'TupleLiteralComma', text: ','},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'z'},
 // CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 6},
@@ -146,7 +134,7 @@ fn F() {
 // 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: 'PrefixOperatorRef', 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},
@@ -158,7 +146,7 @@ fn F() {
 // 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: 'PrefixOperatorRef', text: 'ref', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'StructLiteralField', text: '=', subtree_size: 5},
 // CHECK:STDOUT:             {kind: 'StructLiteralComma', text: ','},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameNotBeforeParams', text: 'z'},
@@ -171,31 +159,3 @@ fn F() {
 // 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:   ]

+ 90 - 0
toolchain/parse/testdata/operators/ref.carbon

@@ -0,0 +1,90 @@
+// 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/operators/ref.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/ref.carbon
+
+// --- ref_precedence.carbon
+
+var x: i32 = ref y as i32;
+
+// --- fail_if_ref.carbon
+
+// CHECK:STDERR: fail_if_ref.carbon:[[@LINE+4]]:18: error: parentheses are required around this unary `if` operator [UnaryOperatorRequiresParentheses]
+// CHECK:STDERR: var x: i32 = ref if b then c else d;
+// CHECK:STDERR:                  ^~
+// CHECK:STDERR:
+var x: i32 = ref if b then c else d;
+var x: i32 = ref (if b then c else d);
+var x: i32 = if b then ref c else ref d;
+
+// CHECK:STDOUT: - filename: ref_precedence.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'VarBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'VariablePattern', text: 'var', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'y'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorRef', text: 'ref', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_if_ref.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'VarBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'VariablePattern', text: 'var', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'b'},
+// CHECK:STDOUT:           {kind: 'IfExprIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'c'},
+// CHECK:STDOUT:           {kind: 'IfExprThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'd'},
+// CHECK:STDOUT:         {kind: 'IfExprElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorRef', text: 'ref', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 14},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'VarBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'VariablePattern', text: 'var', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:           {kind: 'ParenExprStart', text: '('},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'b'},
+// CHECK:STDOUT:             {kind: 'IfExprIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'c'},
+// CHECK:STDOUT:             {kind: 'IfExprThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'd'},
+// CHECK:STDOUT:           {kind: 'IfExprElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'ParenExpr', text: ')', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorRef', text: 'ref', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 16},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'VarBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'VariablePattern', text: 'var', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
+// CHECK:STDOUT:         {kind: 'IfExprIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'c'},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorRef', text: 'ref', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IfExprThen', text: 'then', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'd'},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorRef', text: 'ref', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IfExprElse', text: 'else', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 15},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 0 - 9
toolchain/parse/typed_nodes.h

@@ -1006,15 +1006,6 @@ struct CallExpr {
   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(