Преглед изворни кода

Move diagnostic definitions to be closer to where they emit (#1169)

Jon Meow пре 4 година
родитељ
комит
a24963ea6b

+ 18 - 19
toolchain/lexer/numeric_literal.cpp

@@ -31,25 +31,6 @@ static auto operator<<(llvm::raw_ostream& out, LexedNumericLiteral::Radix radix)
   return out;
 }
 
-CARBON_DIAGNOSTIC(InvalidDigitSeparator, Error,
-                  "Misplaced digit separator in numeric literal.");
-CARBON_DIAGNOSTIC(InvalidDigit, Error,
-                  "Invalid digit '{0}' in {1} numeric literal.", char,
-                  LexedNumericLiteral::Radix);
-CARBON_DIAGNOSTIC(EmptyDigitSequence, Error,
-                  "Empty digit sequence in numeric literal.");
-CARBON_DIAGNOSTIC(
-    IrregularDigitSeparators, Error,
-    "Digit separators in {0} number should appear every {1} characters "
-    "from the right.",
-    LexedNumericLiteral::Radix, int);
-CARBON_DIAGNOSTIC(UnknownBaseSpecifier, Error,
-                  "Unknown base specifier in numeric literal.");
-CARBON_DIAGNOSTIC(BinaryRealLiteral, Error,
-                  "Binary real number literals are not supported.");
-CARBON_DIAGNOSTIC(WrongRealLiteralExponent, Error,
-                  "Expected '{0}' to introduce exponent.", char);
-
 auto LexedNumericLiteral::Lex(llvm::StringRef source_text)
     -> llvm::Optional<LexedNumericLiteral> {
   LexedNumericLiteral result;
@@ -317,17 +298,24 @@ auto LexedNumericLiteral::Parser::CheckDigitSequence(
       // next to another digit separator, or at the end.
       if (!allow_digit_separators || i == 0 || text[i - 1] == '_' ||
           i + 1 == n) {
+        CARBON_DIAGNOSTIC(InvalidDigitSeparator, Error,
+                          "Misplaced digit separator in numeric literal.");
         emitter_.Emit(text.begin() + 1, InvalidDigitSeparator);
       }
       ++num_digit_separators;
       continue;
     }
 
+    CARBON_DIAGNOSTIC(InvalidDigit, Error,
+                      "Invalid digit '{0}' in {1} numeric literal.", char,
+                      LexedNumericLiteral::Radix);
     emitter_.Emit(text.begin() + i, InvalidDigit, c, radix);
     return {.ok = false};
   }
 
   if (num_digit_separators == static_cast<int>(text.size())) {
+    CARBON_DIAGNOSTIC(EmptyDigitSequence, Error,
+                      "Empty digit sequence in numeric literal.");
     emitter_.Emit(text.begin(), EmptyDigitSequence);
     return {.ok = false};
   }
@@ -358,6 +346,11 @@ auto LexedNumericLiteral::Parser::CheckDigitSeparatorPlacement(
   }
 
   auto diagnose_irregular_digit_separators = [&]() {
+    CARBON_DIAGNOSTIC(
+        IrregularDigitSeparators, Error,
+        "Digit separators in {0} number should appear every {1} characters "
+        "from the right.",
+        LexedNumericLiteral::Radix, int);
     emitter_.Emit(text.begin(), IrregularDigitSeparators, radix,
                   radix == Radix::Decimal ? 3 : 4);
   };
@@ -387,6 +380,8 @@ auto LexedNumericLiteral::Parser::CheckDigitSeparatorPlacement(
 auto LexedNumericLiteral::Parser::CheckLeadingZero() -> bool {
   if (radix_ == Radix::Decimal && int_part_.startswith("0") &&
       int_part_ != "0") {
+    CARBON_DIAGNOSTIC(UnknownBaseSpecifier, Error,
+                      "Unknown base specifier in numeric literal.");
     emitter_.Emit(int_part_.begin(), UnknownBaseSpecifier);
     return false;
   }
@@ -408,6 +403,8 @@ auto LexedNumericLiteral::Parser::CheckFractionalPart() -> bool {
   }
 
   if (radix_ == Radix::Binary) {
+    CARBON_DIAGNOSTIC(BinaryRealLiteral, Error,
+                      "Binary real number literals are not supported.");
     emitter_.Emit(literal_.text_.begin() + literal_.radix_point_,
                   BinaryRealLiteral);
     // Carry on and parse the binary real literal anyway.
@@ -429,6 +426,8 @@ auto LexedNumericLiteral::Parser::CheckExponentPart() -> bool {
 
   char expected_exponent_kind = (radix_ == Radix::Decimal ? 'e' : 'p');
   if (literal_.text_[literal_.exponent_] != expected_exponent_kind) {
+    CARBON_DIAGNOSTIC(WrongRealLiteralExponent, Error,
+                      "Expected '{0}' to introduce exponent.", char);
     emitter_.Emit(literal_.text_.begin() + literal_.exponent_,
                   WrongRealLiteralExponent, expected_exponent_kind);
     return false;

+ 31 - 31
toolchain/lexer/string_literal.cpp

@@ -17,37 +17,6 @@ namespace Carbon {
 
 using LexerDiagnosticEmitter = DiagnosticEmitter<const char*>;
 
-CARBON_DIAGNOSTIC(
-    ContentBeforeStringTerminator, Error,
-    "Only whitespace is permitted before the closing `\"\"\"` of a "
-    "multi-line string.");
-CARBON_DIAGNOSTIC(
-    UnicodeEscapeTooLarge, Error,
-    "Code point specified by `\\u{{...}}` escape is greater than 0x10FFFF.");
-CARBON_DIAGNOSTIC(
-    UnicodeEscapeSurrogate, Error,
-    "Code point specified by `\\u{{...}}` escape is a surrogate character.");
-CARBON_DIAGNOSTIC(
-    UnicodeEscapeMissingBracedDigits, Error,
-    "Escape sequence `\\u` must be followed by a braced sequence of "
-    "uppercase hexadecimal digits, for example `\\u{{70AD}}`.");
-CARBON_DIAGNOSTIC(HexadecimalEscapeMissingDigits, Error,
-                  "Escape sequence `\\x` must be followed by two "
-                  "uppercase hexadecimal digits, for example `\\x0F`.");
-CARBON_DIAGNOSTIC(
-    DecimalEscapeSequence, Error,
-    "Decimal digit follows `\\0` escape sequence. Use `\\x00` instead "
-    "of `\\0` if the next character is a digit.");
-CARBON_DIAGNOSTIC(UnknownEscapeSequence, Error,
-                  "Unrecognized escape sequence `{0}`.", char);
-CARBON_DIAGNOSTIC(MismatchedIndentInString, Error,
-                  "Indentation does not match that of the closing \"\"\" in "
-                  "multi-line string literal.");
-CARBON_DIAGNOSTIC(
-    InvalidHorizontalWhitespaceInString, Error,
-    "Whitespace other than plain space must be expressed with an escape "
-    "sequence in a string literal.");
-
 static constexpr char MultiLineIndicator[] = R"(""")";
 
 // Return the number of opening characters of a multi-line string literal,
@@ -182,6 +151,10 @@ static auto CheckIndent(LexerDiagnosticEmitter& emitter, llvm::StringRef text,
   // The last line is not permitted to contain any content after its
   // indentation.
   if (indent.end() != content.end()) {
+    CARBON_DIAGNOSTIC(
+        ContentBeforeStringTerminator, Error,
+        "Only whitespace is permitted before the closing `\"\"\"` of a "
+        "multi-line string.");
     emitter.Emit(indent.end(), ContentBeforeStringTerminator);
   }
 
@@ -197,11 +170,17 @@ static auto ExpandUnicodeEscapeSequence(LexerDiagnosticEmitter& emitter,
     return false;
   }
   if (digits.getAsInteger(16, code_point) || code_point > 0x10FFFF) {
+    CARBON_DIAGNOSTIC(UnicodeEscapeTooLarge, Error,
+                      "Code point specified by `\\u{{...}}` escape is greater "
+                      "than 0x10FFFF.");
     emitter.Emit(digits.begin(), UnicodeEscapeTooLarge);
     return false;
   }
 
   if (code_point >= 0xD800 && code_point < 0xE000) {
+    CARBON_DIAGNOSTIC(UnicodeEscapeSurrogate, Error,
+                      "Code point specified by `\\u{{...}}` escape is a "
+                      "surrogate character.");
     emitter.Emit(digits.begin(), UnicodeEscapeSurrogate);
     return false;
   }
@@ -255,6 +234,10 @@ static auto ExpandAndConsumeEscapeSequence(LexerDiagnosticEmitter& emitter,
     case '0':
       result += '\0';
       if (!content.empty() && IsDecimalDigit(content.front())) {
+        CARBON_DIAGNOSTIC(
+            DecimalEscapeSequence, Error,
+            "Decimal digit follows `\\0` escape sequence. Use `\\x00` instead "
+            "of `\\0` if the next character is a digit.");
         emitter.Emit(content.begin(), DecimalEscapeSequence);
         return;
       }
@@ -267,6 +250,9 @@ static auto ExpandAndConsumeEscapeSequence(LexerDiagnosticEmitter& emitter,
         content = content.drop_front(2);
         return;
       }
+      CARBON_DIAGNOSTIC(HexadecimalEscapeMissingDigits, Error,
+                        "Escape sequence `\\x` must be followed by two "
+                        "uppercase hexadecimal digits, for example `\\x0F`.");
       emitter.Emit(content.begin(), HexadecimalEscapeMissingDigits);
       break;
     case 'u': {
@@ -282,10 +268,16 @@ static auto ExpandAndConsumeEscapeSequence(LexerDiagnosticEmitter& emitter,
           return;
         }
       }
+      CARBON_DIAGNOSTIC(
+          UnicodeEscapeMissingBracedDigits, Error,
+          "Escape sequence `\\u` must be followed by a braced sequence of "
+          "uppercase hexadecimal digits, for example `\\u{{70AD}}`.");
       emitter.Emit(content.begin(), UnicodeEscapeMissingBracedDigits);
       break;
     }
     default:
+      CARBON_DIAGNOSTIC(UnknownEscapeSequence, Error,
+                        "Unrecognized escape sequence `{0}`.", char);
       emitter.Emit(content.begin() - 1, UnknownEscapeSequence, first);
       break;
   }
@@ -315,6 +307,10 @@ static auto ExpandEscapeSequencesAndRemoveIndent(
       const char* line_start = contents.begin();
       contents = contents.drop_while(IsHorizontalWhitespace);
       if (!contents.startswith("\n")) {
+        CARBON_DIAGNOSTIC(
+            MismatchedIndentInString, Error,
+            "Indentation does not match that of the closing \"\"\" in "
+            "multi-line string literal.");
         emitter.Emit(line_start, MismatchedIndentInString);
       }
     }
@@ -354,6 +350,10 @@ static auto ExpandEscapeSequencesAndRemoveIndent(
             contents[after_space] != '\n') {
           // TODO: Include the source range of the whitespace up to
           // `contents.begin() + after_space` in the diagnostic.
+          CARBON_DIAGNOSTIC(
+              InvalidHorizontalWhitespaceInString, Error,
+              "Whitespace other than plain space must be expressed with an "
+              "escape sequence in a string literal.");
           emitter.Emit(contents.begin(), InvalidHorizontalWhitespaceInString);
           // Include the whitespace in the string contents for error recovery.
           result += contents.substr(0, after_space);

+ 15 - 12
toolchain/lexer/tokenized_buffer.cpp

@@ -27,18 +27,6 @@
 
 namespace Carbon {
 
-CARBON_DIAGNOSTIC(TrailingComment, Error,
-                  "Trailing comments are not permitted.");
-CARBON_DIAGNOSTIC(NoWhitespaceAfterCommentIntroducer, Error,
-                  "Whitespace is required after '//'.");
-CARBON_DIAGNOSTIC(UnmatchedClosing, Error,
-                  "Closing symbol without a corresponding opening symbol.");
-CARBON_DIAGNOSTIC(MismatchedClosing, Error,
-                  "Closing symbol does not match most recent opening symbol.");
-CARBON_DIAGNOSTIC(UnrecognizedCharacters, Error,
-                  "Encountered unrecognized characters while parsing.");
-CARBON_DIAGNOSTIC(UnterminatedString, Error, "String is missing a terminator.");
-
 // TODO: Move Overload and VariantMatch somewhere more central.
 
 // Form an overload set from a list of functions. For example:
@@ -131,10 +119,15 @@ class TokenizedBuffer::Lexer {
       if (source_text.startswith("//")) {
         // Any comment must be the only non-whitespace on the line.
         if (set_indent_) {
+          CARBON_DIAGNOSTIC(TrailingComment, Error,
+                            "Trailing comments are not permitted.");
+
           emitter_.Emit(source_text.begin(), TrailingComment);
         }
         // The introducer '//' must be followed by whitespace or EOF.
         if (source_text.size() > 2 && !IsSpace(source_text[2])) {
+          CARBON_DIAGNOSTIC(NoWhitespaceAfterCommentIntroducer, Error,
+                            "Whitespace is required after '//'.");
           emitter_.Emit(source_text.begin() + 2,
                         NoWhitespaceAfterCommentIntroducer);
         }
@@ -285,6 +278,8 @@ class TokenizedBuffer::Lexer {
           literal->ComputeValue(emitter_));
       return token;
     } else {
+      CARBON_DIAGNOSTIC(UnterminatedString, Error,
+                        "String is missing a terminator.");
       emitter_.Emit(literal->text().begin(), UnterminatedString);
       return buffer_.AddToken({.kind = TokenKind::Error(),
                                .token_line = string_line,
@@ -335,6 +330,9 @@ class TokenizedBuffer::Lexer {
       closing_token_info.kind = TokenKind::Error();
       closing_token_info.error_length = kind.GetFixedSpelling().size();
 
+      CARBON_DIAGNOSTIC(
+          UnmatchedClosing, Error,
+          "Closing symbol without a corresponding opening symbol.");
       emitter_.Emit(location, UnmatchedClosing);
       // Note that this still returns true as we do consume a symbol.
       return token;
@@ -412,6 +410,9 @@ class TokenizedBuffer::Lexer {
       }
 
       open_groups_.pop_back();
+      CARBON_DIAGNOSTIC(
+          MismatchedClosing, Error,
+          "Closing symbol does not match most recent opening symbol.");
       token_emitter_.Emit(opening_token, MismatchedClosing);
 
       CHECK(!buffer_.tokens().empty()) << "Must have a prior opening token!";
@@ -510,6 +511,8 @@ class TokenizedBuffer::Lexer {
          .token_line = current_line_,
          .column = current_column_,
          .error_length = static_cast<int32_t>(error_text.size())});
+    CARBON_DIAGNOSTIC(UnrecognizedCharacters, Error,
+                      "Encountered unrecognized characters while parsing.");
     emitter_.Emit(error_text.begin(), UnrecognizedCharacters);
 
     current_column_ += error_text.size();

+ 45 - 42
toolchain/parser/parser_impl.cpp

@@ -17,8 +17,8 @@
 
 namespace Carbon {
 
-CARBON_DIAGNOSTIC(StackLimitExceeded, Error, "Exceeded recursion limit ({0})",
-                  int);
+CARBON_DIAGNOSTIC(ExpectedSemiAfterExpression, Error,
+                  "Expected `;` after expression.");
 
 // Manages the parser's stack depth, particularly decrementing on destruction.
 // This should only be instantiated through RETURN_IF_STACK_LIMITED.
@@ -31,6 +31,8 @@ class ParseTree::Parser::ScopedStackStep {
 
   auto VerifyUnderLimit() -> bool {
     if (parser_->stack_depth_ >= StackDepthLimit) {
+      CARBON_DIAGNOSTIC(StackLimitExceeded, Error,
+                        "Exceeded recursion limit ({0})", int);
       parser_->emitter_.Emit(*parser_->position_, StackLimitExceeded,
                              ParseTree::StackDepthLimit);
       return false;
@@ -74,46 +76,6 @@ static auto operator<<(llvm::raw_ostream& out, RelativeLocation loc)
   return out;
 }
 
-CARBON_DIAGNOSTIC(ExpectedFunctionName, Error,
-                  "Expected function name after `fn` keyword.");
-CARBON_DIAGNOSTIC(ExpectedFunctionParams, Error,
-                  "Expected `(` after function name.");
-CARBON_DIAGNOSTIC(
-    ExpectedFunctionBodyOrSemi, Error,
-    "Expected function definition or `;` after function declaration.");
-CARBON_DIAGNOSTIC(ExpectedVariableName, Error,
-                  "Expected pattern in `var` declaration.");
-CARBON_DIAGNOSTIC(ExpectedParameterName, Error,
-                  "Expected parameter declaration.");
-CARBON_DIAGNOSTIC(ExpectedStructLiteralField, Error, "Expected {0}{1}{2}.",
-                  llvm::StringRef, llvm::StringRef, llvm::StringRef);
-CARBON_DIAGNOSTIC(UnrecognizedDeclaration, Error,
-                  "Unrecognized declaration introducer.");
-CARBON_DIAGNOSTIC(ExpectedCodeBlock, Error, "Expected braced code block.");
-CARBON_DIAGNOSTIC(ExpectedExpression, Error, "Expected expression.");
-CARBON_DIAGNOSTIC(ExpectedParenAfter, Error, "Expected `(` after `{0}`.",
-                  TokenKind);
-CARBON_DIAGNOSTIC(ExpectedCloseParen, Error, "Unexpected tokens before `)`.");
-CARBON_DIAGNOSTIC(ExpectedSemiAfterExpression, Error,
-                  "Expected `;` after expression.");
-CARBON_DIAGNOSTIC(ExpectedSemiAfter, Error, "Expected `;` after `{0}`.",
-                  TokenKind);
-CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDot, Error,
-                  "Expected identifier after `.`.");
-CARBON_DIAGNOSTIC(UnexpectedTokenAfterListElement, Error,
-                  "Expected `,` or `{0}`.", TokenKind);
-CARBON_DIAGNOSTIC(BinaryOperatorRequiresWhitespace, Error,
-                  "Whitespace missing {0} binary operator.", RelativeLocation);
-CARBON_DIAGNOSTIC(UnaryOperatorHasWhitespace, Error,
-                  "Whitespace is not allowed {0} this unary operator.",
-                  RelativeLocation);
-CARBON_DIAGNOSTIC(UnaryOperatorRequiresWhitespace, Error,
-                  "Whitespace is required {0} this unary operator.",
-                  RelativeLocation);
-CARBON_DIAGNOSTIC(
-    OperatorRequiresParentheses, Error,
-    "Parentheses are required to disambiguate operator precedence.");
-
 ParseTree::Parser::Parser(ParseTree& tree_arg, TokenizedBuffer& tokens_arg,
                           TokenDiagnosticEmitter& emitter)
     : tree_(tree_arg),
@@ -322,6 +284,7 @@ auto ParseTree::Parser::ParseCloseParen(TokenizedBuffer::Token open_paren,
   }
 
   // TODO: Include the location of the matching open_paren in the diagnostic.
+  CARBON_DIAGNOSTIC(ExpectedCloseParen, Error, "Unexpected tokens before `)`.");
   emitter_.Emit(*position_, ExpectedCloseParen);
   SkipTo(tokens_.GetMatchedClosingToken(open_paren));
   AddLeafNode(kind, Consume(TokenKind::CloseParen()));
@@ -354,6 +317,8 @@ auto ParseTree::Parser::ParseList(TokenKind open, TokenKind close,
 
       if (!NextTokenIsOneOf({close, TokenKind::Comma()})) {
         if (!element_error) {
+          CARBON_DIAGNOSTIC(UnexpectedTokenAfterListElement, Error,
+                            "Expected `,` or `{0}`.", TokenKind);
           emitter_.Emit(*position_, UnexpectedTokenAfterListElement, close);
         }
         has_errors = true;
@@ -397,10 +362,14 @@ auto ParseTree::Parser::ParsePattern(PatternKind kind) -> llvm::Optional<Node> {
 
   switch (kind) {
     case PatternKind::Parameter:
+      CARBON_DIAGNOSTIC(ExpectedParameterName, Error,
+                        "Expected parameter declaration.");
       emitter_.Emit(*position_, ExpectedParameterName);
       break;
 
     case PatternKind::Variable:
+      CARBON_DIAGNOSTIC(ExpectedVariableName, Error,
+                        "Expected pattern in `var` declaration.");
       emitter_.Emit(*position_, ExpectedVariableName);
       break;
   }
@@ -446,6 +415,7 @@ auto ParseTree::Parser::ParseCodeBlock() -> llvm::Optional<Node> {
       ConsumeIf(TokenKind::OpenCurlyBrace());
   if (!maybe_open_curly) {
     // Recover by parsing a single statement.
+    CARBON_DIAGNOSTIC(ExpectedCodeBlock, Error, "Expected braced code block.");
     emitter_.Emit(*position_, ExpectedCodeBlock);
     return ParseStatement();
   }
@@ -493,6 +463,8 @@ auto ParseTree::Parser::ParseFunctionDeclaration() -> Node {
   auto name_n = ConsumeAndAddLeafNodeIf(TokenKind::Identifier(),
                                         ParseNodeKind::DeclaredName());
   if (!name_n) {
+    CARBON_DIAGNOSTIC(ExpectedFunctionName, Error,
+                      "Expected function name after `fn` keyword.");
     emitter_.Emit(*position_, ExpectedFunctionName);
     // FIXME: We could change the lexer to allow us to synthesize certain
     // kinds of tokens and try to "recover" here, but unclear that this is
@@ -503,6 +475,8 @@ auto ParseTree::Parser::ParseFunctionDeclaration() -> Node {
 
   TokenizedBuffer::Token open_paren = *position_;
   if (tokens_.GetKind(open_paren) != TokenKind::OpenParen()) {
+    CARBON_DIAGNOSTIC(ExpectedFunctionParams, Error,
+                      "Expected `(` after function name.");
     emitter_.Emit(open_paren, ExpectedFunctionParams);
     SkipPastLikelyEnd(function_intro_token, handle_semi_in_error_recovery);
     return add_error_function_node();
@@ -524,6 +498,9 @@ auto ParseTree::Parser::ParseFunctionDeclaration() -> Node {
     }
   } else if (!ConsumeAndAddLeafNodeIf(TokenKind::Semi(),
                                       ParseNodeKind::DeclarationEnd())) {
+    CARBON_DIAGNOSTIC(
+        ExpectedFunctionBodyOrSemi, Error,
+        "Expected function definition or `;` after function declaration.");
     emitter_.Emit(*position_, ExpectedFunctionBodyOrSemi);
     if (tokens_.GetLine(*position_) == tokens_.GetLine(close_paren)) {
       // Only need to skip if we've not already found a new line.
@@ -596,6 +573,8 @@ auto ParseTree::Parser::ParseDeclaration() -> llvm::Optional<Node> {
   }
 
   // We didn't recognize an introducer for a valid declaration.
+  CARBON_DIAGNOSTIC(UnrecognizedDeclaration, Error,
+                    "Unrecognized declaration introducer.");
   emitter_.Emit(*position_, UnrecognizedDeclaration);
 
   // Skip forward past any end of a declaration we simply didn't understand so
@@ -657,6 +636,9 @@ auto ParseTree::Parser::ParseBraceExpression() -> llvm::Optional<Node> {
         auto start_elem = GetSubtreeStartPosition();
 
         auto diagnose_invalid_syntax = [&] {
+          CARBON_DIAGNOSTIC(ExpectedStructLiteralField, Error,
+                            "Expected {0}{1}{2}.", llvm::StringRef,
+                            llvm::StringRef, llvm::StringRef);
           bool can_be_type = kind != Value;
           bool can_be_value = kind != Type;
           emitter_.Emit(*position_, ExpectedStructLiteralField,
@@ -736,6 +718,7 @@ auto ParseTree::Parser::ParsePrimaryExpression() -> llvm::Optional<Node> {
       return ParseBraceExpression();
 
     default:
+      CARBON_DIAGNOSTIC(ExpectedExpression, Error, "Expected expression.");
       emitter_.Emit(*position_, ExpectedExpression);
       return llvm::None;
   }
@@ -753,6 +736,8 @@ auto ParseTree::Parser::ParseDesignatorExpression(SubtreeStart start,
   if (name) {
     AddLeafNode(ParseNodeKind::DesignatedName(), *name);
   } else {
+    CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDot, Error,
+                      "Expected identifier after `.`.");
     emitter_.Emit(*position_, ExpectedIdentifierAfterDot);
     // If we see a keyword, assume it was intended to be the designated name.
     // TODO: Should keywords be valid in designators?
@@ -876,6 +861,9 @@ auto ParseTree::Parser::DiagnoseOperatorFixity(OperatorFixity fixity) -> void {
   if (fixity == OperatorFixity::Infix) {
     // Infix operators must satisfy the infix operator rules.
     if (!is_valid_as_infix) {
+      CARBON_DIAGNOSTIC(BinaryOperatorRequiresWhitespace, Error,
+                        "Whitespace missing {0} binary operator.",
+                        RelativeLocation);
       emitter_.Emit(*position_, BinaryOperatorRequiresWhitespace,
                     tokens_.HasLeadingWhitespace(*position_)
                         ? RelativeLocation::After
@@ -891,12 +879,18 @@ auto ParseTree::Parser::DiagnoseOperatorFixity(OperatorFixity fixity) -> void {
     if (NextTokenKind().IsSymbol() &&
         (prefix ? tokens_.HasTrailingWhitespace(*position_)
                 : tokens_.HasLeadingWhitespace(*position_))) {
+      CARBON_DIAGNOSTIC(UnaryOperatorHasWhitespace, Error,
+                        "Whitespace is not allowed {0} this unary operator.",
+                        RelativeLocation);
       emitter_.Emit(
           *position_, UnaryOperatorHasWhitespace,
           prefix ? RelativeLocation::After : RelativeLocation::Before);
     }
     // Pre/postfix operators must not satisfy the infix operator rules.
     if (is_valid_as_infix) {
+      CARBON_DIAGNOSTIC(UnaryOperatorRequiresWhitespace, Error,
+                        "Whitespace is required {0} this unary operator.",
+                        RelativeLocation);
       emitter_.Emit(
           *position_, UnaryOperatorRequiresWhitespace,
           prefix ? RelativeLocation::Before : RelativeLocation::After);
@@ -930,6 +924,11 @@ auto ParseTree::Parser::IsTrailingOperatorInfix() -> bool {
 
 auto ParseTree::Parser::ParseOperatorExpression(
     PrecedenceGroup ambient_precedence) -> llvm::Optional<Node> {
+  // May be omitted a couple different ways here.
+  CARBON_DIAGNOSTIC(
+      OperatorRequiresParentheses, Error,
+      "Parentheses are required to disambiguate operator precedence.");
+
   RETURN_IF_STACK_LIMITED(llvm::None);
   auto start = GetSubtreeStartPosition();
 
@@ -1047,6 +1046,8 @@ auto ParseTree::Parser::ParseParenCondition(TokenKind introducer)
   auto start = GetSubtreeStartPosition();
   auto open_paren = ConsumeIf(TokenKind::OpenParen());
   if (!open_paren) {
+    CARBON_DIAGNOSTIC(ExpectedParenAfter, Error, "Expected `(` after `{0}`.",
+                      TokenKind);
     emitter_.Emit(*position_, ExpectedParenAfter, introducer);
   }
 
@@ -1113,6 +1114,8 @@ auto ParseTree::Parser::ParseKeywordStatement(ParseNodeKind kind,
   auto semi =
       ConsumeAndAddLeafNodeIf(TokenKind::Semi(), ParseNodeKind::StatementEnd());
   if (!semi) {
+    CARBON_DIAGNOSTIC(ExpectedSemiAfter, Error, "Expected `;` after `{0}`.",
+                      TokenKind);
     emitter_.Emit(*position_, ExpectedSemiAfter, keyword_kind);
     // FIXME: Try to skip to a semicolon to recover.
   }