From 2c45cb3be8a0abacb39b9bcdfbfce0b3af50c854 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 9 Jun 2023 16:41:10 -0700 Subject: [PATCH] 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. --- toolchain/diagnostics/diagnostic_kind.def | 4 ++ toolchain/parser/parse_node_kind.def | 13 +++- toolchain/parser/parser_context.cpp | 13 ++-- toolchain/parser/parser_handle_expression.cpp | 64 ++++++++++++++++++- toolchain/parser/parser_state.def | 29 ++++++++- toolchain/parser/precedence.cpp | 15 +++-- .../testdata/if_expression/basic.carbon | 39 +++++++++++ .../fail_condition_missing.carbon | 31 +++++++++ .../fail_else_expr_missing.carbon | 31 +++++++++ .../if_expression/fail_else_missing.carbon | 31 +++++++++ .../fail_then_expr_missing.carbon | 31 +++++++++ .../if_expression/fail_then_missing.carbon | 31 +++++++++ .../if_expression/fail_top_level_if.carbon | 29 +++++++++ .../testdata/if_expression/precedence.carbon | 37 +++++++++++ ...dence_not.carbon => precedence_not.carbon} | 3 - .../recover_postfix_space_surrounding.carbon | 3 +- .../operators/recover_prefix_space.carbon | 3 +- toolchain/semantics/semantics_handle.cpp | 15 +++++ 18 files changed, 400 insertions(+), 22 deletions(-) create mode 100644 toolchain/parser/testdata/if_expression/basic.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_condition_missing.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_else_missing.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_then_missing.carbon create mode 100644 toolchain/parser/testdata/if_expression/fail_top_level_if.carbon create mode 100644 toolchain/parser/testdata/if_expression/precedence.carbon rename toolchain/parser/testdata/operators/{missing_precedence_not.carbon => precedence_not.carbon} (80%) diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 0916680ed606..832a3535fe15 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/parser/parse_node_kind.def b/toolchain/parser/parse_node_kind.def index 9dc16d8bdff0..7d628c53666e 100644 --- a/toolchain/parser/parse_node_kind.def +++ b/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 diff --git a/toolchain/parser/parser_context.cpp b/toolchain/parser/parser_context.cpp index 8cc000f44cc4..37d3e3bd73ab 100644 --- a/toolchain/parser/parser_context.cpp +++ b/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); diff --git a/toolchain/parser/parser_handle_expression.cpp b/toolchain/parser/parser_handle_expression.cpp index 808c30291bcc..7bae24ab9deb 100644 --- a/toolchain/parser/parser_handle_expression.cpp +++ b/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(); diff --git a/toolchain/parser/parser_state.def b/toolchain/parser/parser_state.def index 9345973ad6f6..b570870ba55a 100644 --- a/toolchain/parser/parser_state.def +++ b/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. // diff --git a/toolchain/parser/precedence.cpp b/toolchain/parser/precedence.cpp index 543512b1d393..cd3889ef66a7 100644 --- a/toolchain/parser/precedence.cpp +++ b/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; } diff --git a/toolchain/parser/testdata/if_expression/basic.carbon b/toolchain/parser/testdata/if_expression/basic.carbon new file mode 100644 index 000000000000..aaf62427d6d1 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_condition_missing.carbon b/toolchain/parser/testdata/if_expression/fail_condition_missing.carbon new file mode 100644 index 000000000000..c8fc41f10493 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon b/toolchain/parser/testdata/if_expression/fail_else_expr_missing.carbon new file mode 100644 index 000000000000..021332457980 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_else_missing.carbon b/toolchain/parser/testdata/if_expression/fail_else_missing.carbon new file mode 100644 index 000000000000..23fcaa30a067 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon b/toolchain/parser/testdata/if_expression/fail_then_expr_missing.carbon new file mode 100644 index 000000000000..3c78e4d1e3e5 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_then_missing.carbon b/toolchain/parser/testdata/if_expression/fail_then_missing.carbon new file mode 100644 index 000000000000..e6ad066d67b6 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/fail_top_level_if.carbon b/toolchain/parser/testdata/if_expression/fail_top_level_if.carbon new file mode 100644 index 000000000000..11421c6c9870 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/if_expression/precedence.carbon b/toolchain/parser/testdata/if_expression/precedence.carbon new file mode 100644 index 000000000000..d2c5dc990794 --- /dev/null +++ b/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; +} diff --git a/toolchain/parser/testdata/operators/missing_precedence_not.carbon b/toolchain/parser/testdata/operators/precedence_not.carbon similarity index 80% rename from toolchain/parser/testdata/operators/missing_precedence_not.carbon rename to toolchain/parser/testdata/operators/precedence_not.carbon index f6746107fec8..4ae4da2102a9 100644 --- a/toolchain/parser/testdata/operators/missing_precedence_not.carbon +++ b/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; } diff --git a/toolchain/parser/testdata/operators/recover_postfix_space_surrounding.carbon b/toolchain/parser/testdata/operators/recover_postfix_space_surrounding.carbon index 0e77453ab909..15ef953ebfdd 100644 --- a/toolchain/parser/testdata/operators/recover_postfix_space_surrounding.carbon +++ b/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 * ; diff --git a/toolchain/parser/testdata/operators/recover_prefix_space.carbon b/toolchain/parser/testdata/operators/recover_prefix_space.carbon index 350e71c8d302..eb065a9010d5 100644 --- a/toolchain/parser/testdata/operators/recover_prefix_space.carbon +++ b/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; diff --git a/toolchain/semantics/semantics_handle.cpp b/toolchain/semantics/semantics_handle.cpp index 516aac542bb3..7c89cbb2d6f4 100644 --- a/toolchain/semantics/semantics_handle.cpp +++ b/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");