Explorar el Código

Parsing, semantic analysis, and lowering for `and`, `or`, `not`. (#2897)

Lowering for `and` and `or` is not yet complete because `Branch` lowering isn't done yet.
Richard Smith hace 2 años
padre
commit
06ce3b0161
Se han modificado 28 ficheros con 470 adiciones y 55 borrados
  1. 9 0
      toolchain/lexer/token_kind.cpp
  2. 12 2
      toolchain/lexer/token_kind.def
  3. 4 0
      toolchain/lexer/token_kind.h
  4. 1 0
      toolchain/lexer/tokenized_buffer.cpp
  5. 8 0
      toolchain/lexer/tokenized_buffer.h
  6. 7 0
      toolchain/lowering/lowering_handle.cpp
  7. 17 0
      toolchain/lowering/testdata/operators/not.carbon
  8. 7 0
      toolchain/parser/parse_node_kind.def
  9. 5 4
      toolchain/parser/parse_tree.cpp
  10. 1 1
      toolchain/parser/parse_tree.h
  11. 9 1
      toolchain/parser/parser_handle_expression.cpp
  12. 13 10
      toolchain/parser/testdata/if_expression/precedence.carbon
  13. 8 6
      toolchain/parser/testdata/operators/associative.carbon
  14. 8 6
      toolchain/parser/testdata/operators/fail_precedence_and_or.carbon
  15. 8 6
      toolchain/parser/testdata/operators/fail_precedence_or_and.carbon
  16. 10 8
      toolchain/parser/testdata/operators/precedence_not.carbon
  17. 9 0
      toolchain/parser/testdata/operators/prefix.carbon
  18. 7 0
      toolchain/semantics/semantics_context.cpp
  19. 4 0
      toolchain/semantics/semantics_context.h
  20. 97 8
      toolchain/semantics/semantics_handle.cpp
  21. 1 3
      toolchain/semantics/semantics_handle_if_expression.cpp
  22. 1 0
      toolchain/semantics/semantics_ir.cpp
  23. 9 0
      toolchain/semantics/semantics_node.h
  24. 7 0
      toolchain/semantics/semantics_node_block_stack.h
  25. 1 0
      toolchain/semantics/semantics_node_kind.def
  26. 78 0
      toolchain/semantics/testdata/operators/and.carbon
  27. 80 0
      toolchain/semantics/testdata/operators/or.carbon
  28. 49 0
      toolchain/semantics/testdata/operators/unary_op.carbon

+ 9 - 0
toolchain/lexer/token_kind.cpp

@@ -104,4 +104,13 @@ auto TokenKind::fixed_spelling() const -> llvm::StringRef {
   return Table[AsInt()];
 }
 
+auto TokenKind::expected_parse_tree_size() const -> int {
+  static constexpr int8_t Table[] = {
+#define CARBON_TOKEN(Name) 1,
+#define CARBON_TOKEN_WITH_VIRTUAL_NODE(size) 1 + size
+#include "toolchain/lexer/token_kind.def"
+  };
+  return Table[AsInt()];
+}
+
 }  // namespace Carbon

+ 12 - 2
toolchain/lexer/token_kind.def

@@ -21,6 +21,9 @@
 //   - CARBON_KEYWORD_TOKEN(Name, Spelling)
 //     Defines a keyword which has the provided spelling, such as `if`.
 //     Spellings must be unique.
+//   - CARBON_TOKEN_WITH_VIRTUAL_NODE(Token)
+//     Wrapped around one of the abokve _TOKEN macros, indicates that this
+//     token has one additional virtual node in the parse tree.
 //
 // This tree represents the subset relationship between these macros, where if a
 // specific x-macro isn't defined, it'll fall back to the parent macro.
@@ -33,6 +36,10 @@
 #define CARBON_SYMBOL_TOKEN(Name, Spelling) CARBON_TOKEN(Name)
 #endif
 
+#ifndef CARBON_TOKEN_WITH_VIRTUAL_NODE
+#define CARBON_TOKEN_WITH_VIRTUAL_NODE(Name) Name
+#endif
+
 // Note that symbols need to be ordered from longest to shortest to effectively
 // provide max-munch lexing.
 
@@ -116,7 +123,8 @@ CARBON_CLOSING_GROUP_SYMBOL_TOKEN(CloseSquareBracket, "]", OpenSquareBracket)
 CARBON_KEYWORD_TOKEN(Abstract,            "abstract")
 CARBON_KEYWORD_TOKEN(Addr,                "addr")
 CARBON_KEYWORD_TOKEN(Alias,               "alias")
-CARBON_KEYWORD_TOKEN(And,                 "and")
+CARBON_TOKEN_WITH_VIRTUAL_NODE(
+  CARBON_KEYWORD_TOKEN(And,               "and"))
 CARBON_KEYWORD_TOKEN(Api,                 "api")
 CARBON_KEYWORD_TOKEN(As,                  "as")
 CARBON_KEYWORD_TOKEN(Auto,                "auto")
@@ -149,7 +157,8 @@ CARBON_KEYWORD_TOKEN(Match,               "match")
 CARBON_KEYWORD_TOKEN(Namespace,           "namespace")
 CARBON_KEYWORD_TOKEN(Not,                 "not")
 CARBON_KEYWORD_TOKEN(Observe,             "observe")
-CARBON_KEYWORD_TOKEN(Or,                  "or")
+CARBON_TOKEN_WITH_VIRTUAL_NODE(
+  CARBON_KEYWORD_TOKEN(Or,                "or"))
 CARBON_KEYWORD_TOKEN(Override,            "override")
 CARBON_KEYWORD_TOKEN(Package,             "package")
 CARBON_KEYWORD_TOKEN(Partial,             "partial")
@@ -186,3 +195,4 @@ CARBON_TOKEN(Error)
 CARBON_TOKEN(EndOfFile)
 
 #undef CARBON_TOKEN
+#undef CARBON_TOKEN_WITH_VIRTUAL_NODE

+ 4 - 0
toolchain/lexer/token_kind.h

@@ -60,6 +60,10 @@ class TokenKind : public CARBON_ENUM_BASE(TokenKind) {
   // Otherwise returns an empty string.
   [[nodiscard]] auto fixed_spelling() const -> llvm::StringRef;
 
+  // Get the expected number of parse tree nodes that will be created for this
+  // token.
+  [[nodiscard]] auto expected_parse_tree_size() const -> int;
+
   // Test whether this token kind is in the provided list.
   [[nodiscard]] auto IsOneOf(std::initializer_list<TokenKind> kinds) const
       -> bool {

+ 1 - 0
toolchain/lexer/tokenized_buffer.cpp

@@ -876,6 +876,7 @@ auto TokenizedBuffer::GetTokenInfo(Token token) const -> const TokenInfo& {
 
 auto TokenizedBuffer::AddToken(TokenInfo info) -> Token {
   token_infos_.push_back(info);
+  expected_parse_tree_size_ += info.kind.expected_parse_tree_size();
   return Token(static_cast<int>(token_infos_.size()) - 1);
 }
 

+ 8 - 0
toolchain/lexer/tokenized_buffer.h

@@ -284,6 +284,10 @@ class TokenizedBuffer {
 
   [[nodiscard]] auto size() const -> int { return token_infos_.size(); }
 
+  [[nodiscard]] auto expected_parse_tree_size() const -> int {
+    return expected_parse_tree_size_;
+  }
+
  private:
   // Implementation detail struct implementing the actual lexer logic.
   class Lexer;
@@ -403,6 +407,10 @@ class TokenizedBuffer {
 
   llvm::DenseMap<llvm::StringRef, Identifier> identifier_map_;
 
+  // The number of parse tree nodes that we expect to be created for the tokens
+  // in this buffer.
+  int expected_parse_tree_size_ = 0;
+
   bool has_errors_ = false;
 };
 

+ 7 - 0
toolchain/lowering/lowering_handle.cpp

@@ -209,6 +209,13 @@ auto LoweringHandleStubReference(LoweringContext& context,
   context.SetLocal(node_id, context.GetLocal(node.GetAsStubReference()));
 }
 
+auto LoweringHandleUnaryOperatorNot(LoweringContext& context,
+                                    SemanticsNodeId node_id, SemanticsNode node)
+    -> void {
+  context.SetLocal(node_id, context.builder().CreateNot(context.GetLocal(
+                                node.GetAsUnaryOperatorNot())));
+}
+
 auto LoweringHandleVarStorage(LoweringContext& context, SemanticsNodeId node_id,
                               SemanticsNode node) -> void {
   // TODO: This should provide a name, not just `var`. Also, LLVM requires

+ 17 - 0
toolchain/lowering/testdata/operators/not.carbon

@@ -0,0 +1,17 @@
+// 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: ; ModuleID = 'not.carbon'
+// CHECK:STDOUT: source_filename = "not.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @Not(i1 %b) {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = xor i1 %b, true
+// CHECK:STDOUT:   ret i1 %0
+// CHECK:STDOUT: }
+
+fn Not(b: bool) -> bool {
+  return not b;
+}

+ 7 - 0
toolchain/parser/parse_node_kind.def

@@ -254,6 +254,13 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator, 1)
 // InfixOperator
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2)
 
+// The first operand of a short-circuiting infix operator:
+//     _external_: expression
+//   ShortCircuitOperand
+//   _external_: expression
+// _external_: InfixOperator
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperand, 1)
+
 // A postfix operator:
 //   _external_: expression
 // PostfixOperator

+ 5 - 4
toolchain/parser/parse_tree.cpp

@@ -310,12 +310,13 @@ auto ParseTree::Verify() const -> ErrorOr<Success> {
     prev_index = n.index;
   }
 
-  if (!has_errors_ &&
-      static_cast<int32_t>(node_impls_.size()) != tokens_->size()) {
+  if (!has_errors_ && static_cast<int32_t>(node_impls_.size()) !=
+                          tokens_->expected_parse_tree_size()) {
     return Error(
         llvm::formatv("ParseTree has {0} nodes and no errors, but "
-                      "TokenizedBuffer has {1} tokens.",
-                      node_impls_.size(), tokens_->size()));
+                      "TokenizedBuffer expected {1} nodes for {2} tokens.",
+                      node_impls_.size(), tokens_->expected_parse_tree_size(),
+                      tokens_->size()));
   }
   return Success();
 }

+ 1 - 1
toolchain/parser/parse_tree.h

@@ -209,7 +209,7 @@ class ParseTree {
   // be used to actually parse the tokens into a tree.
   explicit ParseTree(TokenizedBuffer& tokens_arg) : tokens_(&tokens_arg) {
     // If the tree is valid, there will be one node per token, so reserve once.
-    node_impls_.reserve(tokens_->size());
+    node_impls_.reserve(tokens_->expected_parse_tree_size());
   }
 
   // Prints a single node for Print(). Returns true when preorder and there are

+ 9 - 1
toolchain/parser/parser_handle_expression.cpp

@@ -140,8 +140,9 @@ auto ParserHandleExpressionInPostfixLoop(ParserContext& context) -> void {
 auto ParserHandleExpressionLoop(ParserContext& context) -> void {
   auto state = context.PopState();
 
+  auto operator_kind = context.PositionKind();
   auto trailing_operator = PrecedenceGroup::ForTrailing(
-      context.PositionKind(), context.IsTrailingOperatorInfix());
+      operator_kind, context.IsTrailingOperatorInfix());
   if (!trailing_operator) {
     if (state.has_error) {
       context.ReturnErrorOnState();
@@ -181,6 +182,13 @@ auto ParserHandleExpressionLoop(ParserContext& context) -> void {
   state.lhs_precedence = operator_precedence;
 
   if (is_binary) {
+    if (operator_kind == TokenKind::And || operator_kind == TokenKind::Or) {
+      // For `and` and `or`, wrap the first operand in a virtual parse tree
+      // node so that semantics can insert control flow here.
+      context.AddNode(ParseNodeKind::ShortCircuitOperand, state.token,
+                      state.subtree_start, state.has_error);
+    }
+
     state.state = ParserState::ExpressionLoopForBinary;
     context.PushState(state);
     context.PushStateForExpression(operator_precedence);

+ 13 - 10
toolchain/parser/testdata/if_expression/precedence.carbon

@@ -15,20 +15,23 @@
 // 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: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:           {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 5},
 // 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', subtree_size: 4},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'ShortCircuitOperand', text: 'or', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'or', subtree_size: 3},
-// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', subtree_size: 12},
-// CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 14},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 25},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'or', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', subtree_size: 15},
+// CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 17},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 28},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 8 - 6
toolchain/parser/testdata/operators/associative.carbon

@@ -9,13 +9,15 @@
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'a'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'NameReference', text: 'a'},
+// CHECK:STDOUT:           {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'NameReference', text: 'c'},
-// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', subtree_size: 5},
-// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 8 - 6
toolchain/parser/testdata/operators/fail_precedence_and_or.carbon

@@ -9,13 +9,15 @@
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'a'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'NameReference', text: 'a'},
+// CHECK:STDOUT:           {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ShortCircuitOperand', text: 'or', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'NameReference', text: 'c'},
-// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'or', has_error: yes, subtree_size: 5},
-// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'or', has_error: yes, subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 8 - 6
toolchain/parser/testdata/operators/fail_precedence_or_and.carbon

@@ -9,13 +9,15 @@
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'a'},
-// CHECK:STDOUT:         {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'or', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'NameReference', text: 'a'},
+// CHECK:STDOUT:           {kind: 'ShortCircuitOperand', text: 'or', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'or', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ShortCircuitOperand', text: 'and', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'NameReference', text: 'c'},
-// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', has_error: yes, subtree_size: 5},
-// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', has_error: yes, subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 10 - 8
toolchain/parser/testdata/operators/precedence_not.carbon

@@ -9,16 +9,18 @@
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:           {kind: 'NameReference', text: 'a'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'NameReference', text: 'b'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'NameReference', text: 'a'},
+// CHECK:STDOUT:             {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'NameReference', text: 'b'},
+// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'and', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'NameReference', text: 'c'},
 // CHECK:STDOUT:       {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
-// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', subtree_size: 8},
-// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 9},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 15},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'and', subtree_size: 10},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 11},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 9 - 0
toolchain/parser/testdata/operators/prefix.carbon

@@ -12,7 +12,16 @@
 // CHECK:STDOUT:     {kind: 'NameReference', text: 'n'},
 // CHECK:STDOUT:   {kind: 'PrefixOperator', text: '-', subtree_size: 2},
 // CHECK:STDOUT: {kind: 'VariableDeclaration', text: ';', subtree_size: 8},
+// CHECK:STDOUT:   {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:     {kind: 'DeclaredName', text: 'b'},
+// CHECK:STDOUT:     {kind: 'Literal', text: 'bool'},
+// CHECK:STDOUT:   {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:     {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:   {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT: {kind: 'VariableDeclaration', text: ';', subtree_size: 8},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 
 var n: i8 = -n;
+var b: bool = not true;

+ 7 - 0
toolchain/semantics/semantics_context.cpp

@@ -202,6 +202,13 @@ auto SemanticsContext::ImplicitAsRequired(ParseTree::Node parse_node,
   return output_value_id;
 }
 
+auto SemanticsContext::ImplicitAsBool(ParseTree::Node parse_node,
+                                      SemanticsNodeId value_id)
+    -> SemanticsNodeId {
+  return ImplicitAsRequired(parse_node, value_id,
+                            CanonicalizeType(SemanticsNodeId::BuiltinBoolType));
+}
+
 auto SemanticsContext::ImplicitAsImpl(SemanticsNodeId value_id,
                                       SemanticsTypeId as_type_id,
                                       SemanticsNodeId* output_value_id)

+ 4 - 0
toolchain/semantics/semantics_context.h

@@ -76,6 +76,10 @@ class SemanticsContext {
   auto ImplicitAsRequired(ParseTree::Node parse_node, SemanticsNodeId value_id,
                           SemanticsTypeId as_type_id) -> SemanticsNodeId;
 
+  // Runs ImplicitAsRequired for a conversion to `bool`.
+  auto ImplicitAsBool(ParseTree::Node parse_node, SemanticsNodeId value_id)
+      -> SemanticsNodeId;
+
   // Canonicalizes a type which is tracked as a single node.
   // TODO: This should eventually return a type ID.
   auto CanonicalizeType(SemanticsNodeId node_id) -> SemanticsTypeId;

+ 97 - 8
toolchain/semantics/semantics_handle.cpp

@@ -193,21 +193,45 @@ auto SemanticsHandleInfixOperator(SemanticsContext& context,
   auto rhs_id = context.node_stack().Pop<SemanticsNodeId>();
   auto lhs_id = context.node_stack().Pop<SemanticsNodeId>();
 
-  // TODO: This should search for a compatible interface. For now, it's a very
-  // trivial check of validity on the operation.
-  lhs_id = context.ImplicitAsRequired(
-      parse_node, lhs_id, context.semantics_ir().GetNode(rhs_id).type_id());
-
   // Figure out the operator for the token.
   auto token = context.parse_tree().node_token(parse_node);
   switch (auto token_kind = context.tokens().GetKind(token)) {
     case TokenKind::Plus:
+      // TODO: This should search for a compatible interface. For now, it's a
+      // very trivial check of validity on the operation.
+      lhs_id = context.ImplicitAsRequired(
+          parse_node, lhs_id, context.semantics_ir().GetNode(rhs_id).type_id());
+
       context.AddNodeAndPush(
           parse_node,
           SemanticsNode::BinaryOperatorAdd::Make(
               parse_node, context.semantics_ir().GetNode(lhs_id).type_id(),
               lhs_id, rhs_id));
       break;
+
+    case TokenKind::And:
+    case TokenKind::Or: {
+      // The first operand is wrapped in a ShortCircuitOperand, which we
+      // already handled by creating a RHS block and a resumption block, which
+      // are the current block and its enclosing block.
+      rhs_id = context.ImplicitAsBool(parse_node, rhs_id);
+
+      // When the second operand is evaluated, the result of `and` and `or` is
+      // its value.
+      auto rhs_block_id = context.node_block_stack().PopForAdd();
+      auto resume_block_id = context.node_block_stack().PeekForAdd();
+      context.AddNodeToBlock(rhs_block_id,
+                             SemanticsNode::BranchWithArg::Make(
+                                 parse_node, resume_block_id, rhs_id));
+
+      // Collect the result from either the first or second operand.
+      context.AddNodeAndPush(
+          parse_node,
+          SemanticsNode::BlockArg::Make(
+              parse_node, context.semantics_ir().GetNode(rhs_id).type_id()));
+      break;
+    }
+
     default:
       return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
   }
@@ -231,8 +255,8 @@ auto SemanticsHandleLiteral(SemanticsContext& context,
           SemanticsNode::BoolLiteral::Make(
               parse_node,
               context.CanonicalizeType(SemanticsNodeId::BuiltinBoolType),
-              token_kind == TokenKind::True ? SemanticsBoolValue(1)
-                                            : SemanticsBoolValue(0)));
+              token_kind == TokenKind::True ? SemanticsBoolValue::True
+                                            : SemanticsBoolValue::False));
       break;
     }
     case TokenKind::IntegerLiteral: {
@@ -410,7 +434,25 @@ auto SemanticsHandlePostfixOperator(SemanticsContext& context,
 
 auto SemanticsHandlePrefixOperator(SemanticsContext& context,
                                    ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePrefixOperator");
+  auto value_id = context.node_stack().Pop<SemanticsNodeId>();
+
+  // Figure out the operator for the token.
+  auto token = context.parse_tree().node_token(parse_node);
+  switch (auto token_kind = context.tokens().GetKind(token)) {
+    case TokenKind::Not:
+      value_id = context.ImplicitAsBool(parse_node, value_id);
+      context.AddNodeAndPush(
+          parse_node,
+          SemanticsNode::UnaryOperatorNot::Make(
+              parse_node, context.semantics_ir().GetNode(value_id).type_id(),
+              value_id));
+      break;
+
+    default:
+      return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
+  }
+
+  return true;
 }
 
 auto SemanticsHandleReturnStatement(SemanticsContext& context,
@@ -492,6 +534,53 @@ auto SemanticsHandleSelfValueIdentifier(SemanticsContext& context,
   return context.TODO(parse_node, "HandleSelfValueIdentifier");
 }
 
+auto SemanticsHandleShortCircuitOperand(SemanticsContext& context,
+                                        ParseTree::Node parse_node) -> bool {
+  // Convert the condition to `bool`.
+  auto cond_value_id = context.node_stack().Pop<SemanticsNodeId>();
+  cond_value_id = context.ImplicitAsBool(parse_node, cond_value_id);
+  auto bool_type_id = context.semantics_ir().GetNode(cond_value_id).type_id();
+
+  // Compute the branch value: the condition for `and`, inverted for `or`.
+  auto token = context.parse_tree().node_token(parse_node);
+  SemanticsNodeId branch_value_id = SemanticsNodeId::Invalid;
+  auto short_circuit_result_id = SemanticsNodeId::Invalid;
+  switch (auto token_kind = context.tokens().GetKind(token)) {
+    case TokenKind::And:
+      branch_value_id = cond_value_id;
+      short_circuit_result_id =
+          context.AddNode(SemanticsNode::BoolLiteral::Make(
+              parse_node, bool_type_id, SemanticsBoolValue::False));
+      break;
+
+    case TokenKind::Or:
+      branch_value_id = context.AddNode(SemanticsNode::UnaryOperatorNot::Make(
+          parse_node, bool_type_id, cond_value_id));
+      short_circuit_result_id =
+          context.AddNode(SemanticsNode::BoolLiteral::Make(
+              parse_node, bool_type_id, SemanticsBoolValue::True));
+      break;
+
+    default:
+      CARBON_FATAL() << "Unexpected short-circuiting operator " << parse_node;
+  }
+
+  // Create a block for the right-hand side and for the continuation.
+  auto lhs_block_id = context.node_block_stack().PopForAdd();
+  auto end_block_id = context.node_block_stack().PushForAdd();
+  auto rhs_block_id = context.node_block_stack().PushForAdd();
+  context.AddNodeToBlock(
+      lhs_block_id,
+      SemanticsNode::BranchIf::Make(parse_node, rhs_block_id, branch_value_id));
+  context.AddNodeToBlock(
+      lhs_block_id, SemanticsNode::BranchWithArg::Make(
+                        parse_node, end_block_id, short_circuit_result_id));
+
+  // Put the condition back on the stack for SemanticsHandleInfixOperator.
+  context.node_stack().Push(parse_node, cond_value_id);
+  return true;
+}
+
 auto SemanticsHandleTemplate(SemanticsContext& context,
                              ParseTree::Node parse_node) -> bool {
   return context.TODO(parse_node, "HandleTemplate");

+ 1 - 3
toolchain/semantics/semantics_handle_if_expression.cpp

@@ -13,9 +13,7 @@ auto SemanticsHandleIfExpressionIf(SemanticsContext& context,
   context.node_stack().Push(if_node);
 
   // Convert the condition to `bool`.
-  cond_value_id = context.ImplicitAsRequired(
-      if_node, cond_value_id,
-      context.CanonicalizeType(SemanticsNodeId::BuiltinBoolType));
+  cond_value_id = context.ImplicitAsBool(if_node, cond_value_id);
 
   // Stop emitting the current block. We'll add some branch instructions to it
   // later, but we don't want it on the stack any more.

+ 1 - 0
toolchain/semantics/semantics_ir.cpp

@@ -216,6 +216,7 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
       case SemanticsNodeKind::StructMemberAccess:
       case SemanticsNodeKind::StructValue:
       case SemanticsNodeKind::StubReference:
+      case SemanticsNodeKind::UnaryOperatorNot:
       case SemanticsNodeKind::VarStorage:
         // We don't need to handle stringification for nodes that don't show up
         // in errors, but make it clear what's going on so that it's clearer

+ 9 - 0
toolchain/semantics/semantics_node.h

@@ -70,6 +70,9 @@ struct SemanticsCrossReferenceIRId : public IndexBase {
 
 // A boolean value.
 struct SemanticsBoolValue : public IndexBase {
+  static const SemanticsBoolValue False;
+  static const SemanticsBoolValue True;
+
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     switch (index) {
@@ -85,6 +88,9 @@ struct SemanticsBoolValue : public IndexBase {
   }
 };
 
+constexpr SemanticsBoolValue SemanticsBoolValue::False = SemanticsBoolValue(0);
+constexpr SemanticsBoolValue SemanticsBoolValue::True = SemanticsBoolValue(1);
+
 // The ID of an integer literal.
 struct SemanticsIntegerLiteralId : public IndexBase {
   using IndexBase::IndexBase;
@@ -368,6 +374,9 @@ class SemanticsNode {
   using StubReference =
       Factory<SemanticsNodeKind::StubReference, SemanticsNodeId /*node_id*/>;
 
+  using UnaryOperatorNot = Factory<SemanticsNodeKind::UnaryOperatorNot,
+                                   SemanticsNodeId /*operand_id*/>;
+
   using VarStorage = Factory<SemanticsNodeKind::VarStorage>;
 
   SemanticsNode()

+ 7 - 0
toolchain/semantics/semantics_node_block_stack.h

@@ -44,6 +44,13 @@ class SemanticsNodeBlockStack {
   // SemanticsNodeBlockId::Empty is returned if one wasn't allocated.
   auto Pop() -> SemanticsNodeBlockId;
 
+  // Pops the top node block, ensuring that it is lazily allocated if it's
+  // empty. For use when more nodes will be added to the block later.
+  auto PopForAdd() -> SemanticsNodeBlockId {
+    PeekForAdd();
+    return Pop();
+  }
+
   // Prints the stack for a stack dump.
   auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
 

+ 1 - 0
toolchain/semantics/semantics_node_kind.def

@@ -42,6 +42,7 @@ CARBON_SEMANTICS_NODE_KIND(StructType)
 CARBON_SEMANTICS_NODE_KIND(StructTypeField)
 CARBON_SEMANTICS_NODE_KIND(StructValue)
 CARBON_SEMANTICS_NODE_KIND(StubReference)
+CARBON_SEMANTICS_NODE_KIND(UnaryOperatorNot)
 CARBON_SEMANTICS_NODE_KIND(VarStorage)
 
 #undef CARBON_SEMANTICS_NODE_KIND

+ 78 - 0
toolchain/semantics/testdata/operators/and.carbon

@@ -0,0 +1,78 @@
+// 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: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, return_type: type0, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, return_type: type0, body: block3},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, return_type: type0, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   And,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+1, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+4, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: false, type: type0},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block6, arg1: node+7},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+8},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+11},
+// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+13, type: type0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F() -> bool { return true; }
+fn G() -> bool { return true; }
+
+fn And() -> bool {
+  return F() and G();
+}

+ 80 - 0
toolchain/semantics/testdata/operators/or.carbon

@@ -0,0 +1,80 @@
+// 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: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, return_type: type0, body: block2},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, return_type: type0, body: block3},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, return_type: type0, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   Or,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+1, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+4, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0, type: type0},
+// CHECK:STDOUT:   {kind: UnaryOperatorNot, arg0: node+7, type: type0},
+// CHECK:STDOUT:   {kind: BoolLiteral, arg0: true, type: type0},
+// CHECK:STDOUT:   {kind: BranchIf, arg0: block6, arg1: node+8},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+9},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1, type: type0},
+// CHECK:STDOUT:   {kind: BranchWithArg, arg0: block5, arg1: node+12},
+// CHECK:STDOUT:   {kind: BlockArg, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+14, type: type0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F() -> bool { return true; }
+fn G() -> bool { return true; }
+
+fn Or() -> bool {
+  return F() or G();
+}

+ 49 - 0
toolchain/semantics/testdata/operators/unary_op.carbon

@@ -0,0 +1,49 @@
+// 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: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str1, param_refs: block2, return_type: type0, body: block4},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT:   Not,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeBoolType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node+0, type: type0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: UnaryOperatorNot, arg0: node+0, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+3, type: type0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Not(b: bool) -> bool {
+  return not b;
+}