浏览代码

Parsing support for `if` expressions. (#2883)

We model `if a then b else` as a prefix operator for parsing precedence purposes. The rule that a statement starting with `if` is never an `if` expression is handled implicitly because the statement parser never invokes the expression parser for a statement starting with `if`.

This exposed a bug in our diagnosis of the whitespace rule for prefix operators, which was incorrectly being applied to non-symbolic operators in some cases, and was producing a bogus second diagnostic in some cases, which is also fixed here.
Richard Smith 2 年之前
父节点
当前提交
2c45cb3be8

+ 4 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -79,6 +79,10 @@ CARBON_DIAGNOSTIC_KIND(ExpectedSemiToEndPackageDirective)
 CARBON_DIAGNOSTIC_KIND(ExpectedIn)
 CARBON_DIAGNOSTIC_KIND(ExpectedInNotColon)
 
+// If-specific diagnostics.
+CARBON_DIAGNOSTIC_KIND(ExpectedThenAfterIf)
+CARBON_DIAGNOSTIC_KIND(ExpectedElseAfterIf)
+
 // Declaration diagnostics.
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationName)
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemiOrDefinition)

+ 12 - 1
toolchain/parser/parse_node_kind.def

@@ -173,7 +173,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(ForIn, VariableIntroducer)
 CARBON_PARSE_NODE_KIND_BRACKET(ForHeader, ForHeaderStart)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForStatement, 2)
 
-// `if` + `else`:
+// `if` statement + `else`:
 //     IfConditionStart
 //     _external_: expression
 //   IfCondition
@@ -259,6 +259,17 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2)
 // PostfixOperator
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1)
 
+// `if` expression + `then` + `else`:
+//   _external_: expression
+//   IfExpressionThen
+//   _external_: expression
+//   IfExpressionElse
+//   _external_: expression
+// IfExpression
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpression, 5)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionThen, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 0)
+
 // Struct literals, such as `{.a = 0}`:
 //   StructLiteralOrStructTypeLiteralStart
 //       _external_: DesignatedName

+ 8 - 5
toolchain/parser/parser_context.cpp

@@ -313,6 +313,11 @@ auto ParserContext::IsTrailingOperatorInfix() -> bool {
 }
 
 auto ParserContext::DiagnoseOperatorFixity(OperatorFixity fixity) -> void {
+  if (!PositionKind().is_symbol()) {
+    // Whitespace-based fixity rules only apply to symbolic operators.
+    return;
+  }
+
   if (fixity == OperatorFixity::Infix) {
     // Infix operators must satisfy the infix operator rules.
     if (!IsLexicallyValidInfixOperator()) {
@@ -331,8 +336,7 @@ auto ParserContext::DiagnoseOperatorFixity(OperatorFixity fixity) -> void {
 
     // Whitespace is not permitted between a symbolic pre/postfix operator and
     // its operand.
-    if (PositionKind().is_symbol() &&
-        (prefix ? tokens().HasTrailingWhitespace(*position_)
+    if ((prefix ? tokens().HasTrailingWhitespace(*position_)
                 : tokens().HasLeadingWhitespace(*position_))) {
       CARBON_DIAGNOSTIC(UnaryOperatorHasWhitespace, Error,
                         "Whitespace is not allowed {0} this unary operator.",
@@ -340,9 +344,8 @@ auto ParserContext::DiagnoseOperatorFixity(OperatorFixity fixity) -> void {
       emitter_->Emit(
           *position_, UnaryOperatorHasWhitespace,
           prefix ? RelativeLocation::After : RelativeLocation::Before);
-    }
-    // Pre/postfix operators must not satisfy the infix operator rules.
-    if (IsLexicallyValidInfixOperator()) {
+    } else if (IsLexicallyValidInfixOperator()) {
+      // Pre/postfix operators must not satisfy the infix operator rules.
       CARBON_DIAGNOSTIC(UnaryOperatorRequiresWhitespace, Error,
                         "Whitespace is required {0} this unary operator.",
                         RelativeLocation);

+ 61 - 3
toolchain/parser/parser_handle_expression.cpp

@@ -27,9 +27,15 @@ auto ParserHandleExpression(ParserContext& context) -> void {
       context.DiagnoseOperatorFixity(ParserContext::OperatorFixity::Prefix);
     }
 
-    context.PushStateForExpressionLoop(ParserState::ExpressionLoopForPrefix,
-                                       state.ambient_precedence,
-                                       *operator_precedence);
+    if (context.PositionIs(TokenKind::If)) {
+      context.PushState(ParserState::ExpressionIfFinish);
+      context.PushState(ParserState::ExpressionThen);
+    } else {
+      context.PushStateForExpressionLoop(ParserState::ExpressionLoopForPrefix,
+                                         state.ambient_precedence,
+                                         *operator_precedence);
+    }
+
     ++context.position();
     context.PushStateForExpression(*operator_precedence);
   } else {
@@ -203,6 +209,58 @@ auto ParserHandleExpressionLoopForPrefix(ParserContext& context) -> void {
   context.PushState(state);
 }
 
+auto ParserHandleExpressionThen(ParserContext& context) -> void {
+  auto state = context.PopState();
+
+  if (context.ConsumeAndAddLeafNodeIf(TokenKind::Then,
+                                      ParseNodeKind::IfExpressionThen)) {
+    context.PushState(ParserState::ExpressionElse);
+    context.PushStateForExpression(*PrecedenceGroup::ForLeading(TokenKind::If));
+  } else {
+    // TODO: Include the location of the `if` token.
+    CARBON_DIAGNOSTIC(ExpectedThenAfterIf, Error,
+                      "Expected `then` after `if` condition.");
+    if (!state.has_error) {
+      context.emitter().Emit(*context.position(), ExpectedThenAfterIf);
+    }
+    // Add placeholders for `then expression else expression`.
+    for (int i = 0; i != 4; ++i) {
+      context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
+                          /*has_error=*/true);
+    }
+    context.ReturnErrorOnState();
+  }
+}
+
+auto ParserHandleExpressionElse(ParserContext& context) -> void {
+  auto state = context.PopState();
+
+  if (context.ConsumeAndAddLeafNodeIf(TokenKind::Else,
+                                      ParseNodeKind::IfExpressionElse)) {
+    context.PushStateForExpression(*PrecedenceGroup::ForLeading(TokenKind::If));
+  } else {
+    // TODO: Include the location of the `if` token.
+    CARBON_DIAGNOSTIC(ExpectedElseAfterIf, Error,
+                      "Expected `else` after `if ... then ...`.");
+    if (!state.has_error) {
+      context.emitter().Emit(*context.position(), ExpectedElseAfterIf);
+    }
+    // Add placeholders for `else expression`.
+    for (int i = 0; i != 2; ++i) {
+      context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(),
+                          /*has_error=*/true);
+    }
+    context.ReturnErrorOnState();
+  }
+}
+
+auto ParserHandleExpressionIfFinish(ParserContext& context) -> void {
+  auto state = context.PopState();
+
+  context.AddNode(ParseNodeKind::IfExpression, state.token, state.subtree_start,
+                  state.has_error);
+}
+
 auto ParserHandleExpressionStatementFinish(ParserContext& context) -> void {
   auto state = context.PopState();
 

+ 28 - 1
toolchain/parser/parser_state.def

@@ -181,7 +181,11 @@ CARBON_PARSER_STATE_VARIANTS2(Designator, Expression, Struct)
 
 // Handles processing of an expression.
 //
-// If valid prefix operator:
+// If `If`:
+//   1. Expression
+//   2. ExpressionThen
+//   3. ExpressionIfFinish
+// Else if valid prefix operator:
 //   1. Expression
 //   2. ExpressionLoopForPrefix
 // Else:
@@ -242,6 +246,29 @@ CARBON_PARSER_STATE(ExpressionLoopForBinary)
 //   1. ExpressionLoop
 CARBON_PARSER_STATE(ExpressionLoopForPrefix)
 
+// Completes an IfExpression.
+//
+// Always:
+//   (state done)
+CARBON_PARSER_STATE(ExpressionIfFinish)
+
+// Handles the `then` token in an `if` expression.
+//
+// If `Then`:
+//   1. Expression
+//   2. ExpressionElse
+// Else:
+//  (state done)
+CARBON_PARSER_STATE(ExpressionThen)
+
+// Handles the `else` token in an `if` expression.
+//
+// If `Else`:
+//   1. Expression
+// Else:
+//  (state done)
+CARBON_PARSER_STATE(ExpressionElse)
+
 // Handles the `;` for an expression statement, which is different from most
 // keyword statements.
 //

+ 10 - 5
toolchain/parser/precedence.cpp

@@ -37,6 +37,8 @@ enum PrecedenceLevel : int8_t {
   Relational,
   LogicalAnd,
   LogicalOr,
+  // Conditional.
+  If,
   // Assignment.
   SimpleAssignment,
   CompoundAssignment,
@@ -62,11 +64,11 @@ struct OperatorPriorityTable {
     MarkHigherThan({TypePostfix}, {Type});
     MarkHigherThan(
         {Modulo, Additive, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift, Type},
-        {SimpleAssignment, CompoundAssignment, Relational});
+        {Relational});
     MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr});
-    MarkHigherThan(
-        {SimpleAssignment, CompoundAssignment, LogicalAnd, LogicalOr},
-        {Lowest});
+    MarkHigherThan({LogicalAnd, LogicalOr}, {If});
+    MarkHigherThan({If}, {SimpleAssignment, CompoundAssignment});
+    MarkHigherThan({SimpleAssignment, CompoundAssignment}, {Lowest});
 
     // Compute the transitive closure of the above relationships: if we parse
     // `a $ b @ c` as `(a $ b) @ c` and parse `b @ c % d` as `(b @ c) % d`,
@@ -137,7 +139,7 @@ struct OperatorPriorityTable {
     // Ambiguous would mean it's an error. LeftFirst is meaningless. For now we
     // allow all prefix operators to be repeated.
     for (PrecedenceLevel prefix :
-         {TermPrefix, NumericPrefix, BitwisePrefix, LogicalPrefix}) {
+         {TermPrefix, NumericPrefix, BitwisePrefix, LogicalPrefix, If}) {
       table[prefix][prefix] = OperatorPriority::RightFirst;
     }
 
@@ -210,6 +212,9 @@ auto PrecedenceGroup::ForLeading(TokenKind kind)
     case TokenKind::Tilde:
       return PrecedenceGroup(BitwisePrefix);
 
+    case TokenKind::If:
+      return PrecedenceGroup(If);
+
     default:
       return std::nullopt;
   }

+ 39 - 0
toolchain/parser/testdata/if_expression/basic.carbon

@@ -0,0 +1,39 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'DeclaredName', text: 'b'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ParameterListComma', text: ','},
+// CHECK:STDOUT:         {kind: 'DeclaredName', text: 'x'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ParameterListComma', text: ','},
+// CHECK:STDOUT:         {kind: 'DeclaredName', text: 'y'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 13},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:     {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 18},
+// CHECK:STDOUT:     {kind: 'ReturnStatementStart', text: 'return'},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'x'},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'y'},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 27},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F(b: bool, x: i32, y: i32) -> bool {
+  return if b then x else y;
+}

+ 31 - 0
toolchain/parser/testdata/if_expression/fail_condition_missing.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'DeclaredName', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_condition_missing.carbon:[[@LINE+1]]:18: Expected expression.
+  var n: i32 = if;
+}

+ 31 - 0
toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'DeclaredName', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_else_expr_missing.carbon:[[@LINE+1]]:35: Expected expression.
+  var n: i32 = if true then 1 else;
+}

+ 31 - 0
toolchain/parser/testdata/if_expression/fail_else_missing.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'DeclaredName', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_else_missing.carbon:[[@LINE+1]]:30: Expected `else` after `if ... then ...`.
+  var n: i32 = if true then 1;
+}

+ 31 - 0
toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'DeclaredName', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_then_expr_missing.carbon:[[@LINE+1]]:28: Expected expression.
+  var n: i32 = if true then;
+}

+ 31 - 0
toolchain/parser/testdata/if_expression/fail_then_missing.carbon

@@ -0,0 +1,31 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'DeclaredName', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_then_missing.carbon:[[@LINE+1]]:23: Expected `then` after `if` condition.
+  var n: i32 = if true;
+}

+ 29 - 0
toolchain/parser/testdata/if_expression/fail_top_level_if.carbon

@@ -0,0 +1,29 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'IfConditionStart', text: 'if', has_error: yes},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'true'},
+// CHECK:STDOUT:     {kind: 'IfCondition', text: 'if', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'CodeBlockStart', text: 'then', has_error: yes},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: 'then', has_error: yes},
+// CHECK:STDOUT:       {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'CodeBlock', text: 'then', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:   {kind: 'IfStatement', text: 'if', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F() {
+  // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+3]]:6: Expected `(` after `if`.
+  // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+2]]:11: Expected braced code block.
+  // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+1]]:11: Expected expression.
+  if true then 1 else 2;
+}

+ 37 - 0
toolchain/parser/testdata/if_expression/precedence.carbon

@@ -0,0 +1,37 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'DeclaredName', text: 'b'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'NameReference', text: 'bool'},
+// CHECK:STDOUT:     {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'ReturnStatementStart', text: 'return'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'or', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'IfExpression', text: 'if', subtree_size: 12},
+// CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 14},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 25},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+fn F(b: bool) -> bool {
+  return if b and b then b and b else b or b;
+}

+ 0 - 3
toolchain/parser/testdata/operators/missing_precedence_not.carbon → toolchain/parser/testdata/operators/precedence_not.carbon

@@ -23,8 +23,5 @@
 // CHECK:STDOUT: ]
 
 fn F() {
-  // CHECK:STDERR: missing_precedence_not.carbon:[[@LINE+3]]:3: Whitespace is required before this unary operator.
-  // CHECK:STDERR: missing_precedence_not.carbon:[[@LINE+2]]:13: Whitespace is required before this unary operator.
-  // CHECK:STDERR: missing_precedence_not.carbon:[[@LINE+1]]:23: Whitespace is required before this unary operator.
   not a and not b and not c;
 }

+ 1 - 2
toolchain/parser/testdata/operators/recover_postfix_space_surrounding.carbon

@@ -15,6 +15,5 @@
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: recover_postfix_space_surrounding.carbon:[[@LINE+2]]:18: Whitespace is not allowed before this unary operator.
-// CHECK:STDERR: recover_postfix_space_surrounding.carbon:[[@LINE+1]]:18: Whitespace is required after this unary operator.
+// CHECK:STDERR: recover_postfix_space_surrounding.carbon:[[@LINE+1]]:18: Whitespace is not allowed before this unary operator.
 var v: type = i8 * ;

+ 1 - 2
toolchain/parser/testdata/operators/recover_prefix_space.carbon

@@ -15,6 +15,5 @@
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: recover_prefix_space.carbon:[[@LINE+2]]:13: Whitespace is not allowed after this unary operator.
-// CHECK:STDERR: recover_prefix_space.carbon:[[@LINE+1]]:13: Whitespace is required before this unary operator.
+// CHECK:STDERR: recover_prefix_space.carbon:[[@LINE+1]]:13: Whitespace is not allowed after this unary operator.
 var n: i8 = - n;

+ 15 - 0
toolchain/semantics/semantics_handle.cpp

@@ -178,6 +178,21 @@ auto SemanticsHandleIfConditionStart(SemanticsContext& context,
   return context.TODO(parse_node, "HandleIfConditionStart");
 }
 
+auto SemanticsHandleIfExpression(SemanticsContext& context,
+                                 ParseTree::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleIfExpression");
+}
+
+auto SemanticsHandleIfExpressionElse(SemanticsContext& context,
+                                     ParseTree::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleIfExpressionElse");
+}
+
+auto SemanticsHandleIfExpressionThen(SemanticsContext& context,
+                                     ParseTree::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleIfExpressionThen");
+}
+
 auto SemanticsHandleIfStatement(SemanticsContext& context,
                                 ParseTree::Node parse_node) -> bool {
   return context.TODO(parse_node, "HandleIfStatement");