Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -3184,6 +3184,9 @@ * ``LK_None`` (in configuration: ``None``) Do not use. + * ``LK_Carbon`` (in configuration: ``Carbon``) + Should be used for Carbon + * ``LK_Cpp`` (in configuration: ``Cpp``) Should be used for C, C++. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -421,6 +421,7 @@ clang-format ------------ +- Basic Carbon Language Support. clang-extdef-mapping -------------------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -2587,6 +2587,8 @@ enum LanguageKind : int8_t { /// Do not use. LK_None, + /// Should be used for Carbon + LK_Carbon, /// Should be used for C, C++. LK_Cpp, /// Should be used for C#. @@ -2614,6 +2616,7 @@ }; bool isCpp() const { return Language == LK_Cpp || Language == LK_ObjC; } bool isCSharp() const { return Language == LK_CSharp; } + bool isCarbon() const { return Language == LK_Carbon; } bool isJson() const { return Language == LK_Json; } bool isJavaScript() const { return Language == LK_JavaScript; } bool isVerilog() const { return Language == LK_Verilog; } @@ -4292,6 +4295,8 @@ switch (Language) { case FormatStyle::LK_Cpp: return "C++"; + case FormatStyle::LK_Carbon: + return "Carbon"; case FormatStyle::LK_CSharp: return "CSharp"; case FormatStyle::LK_ObjC: Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -60,14 +60,15 @@ template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::LanguageKind &Value) { IO.enumCase(Value, "Cpp", FormatStyle::LK_Cpp); + IO.enumCase(Value, "Carbon", FormatStyle::LK_Carbon); + IO.enumCase(Value, "CSharp", FormatStyle::LK_CSharp); IO.enumCase(Value, "Java", FormatStyle::LK_Java); IO.enumCase(Value, "JavaScript", FormatStyle::LK_JavaScript); + IO.enumCase(Value, "Json", FormatStyle::LK_Json); IO.enumCase(Value, "ObjC", FormatStyle::LK_ObjC); IO.enumCase(Value, "Proto", FormatStyle::LK_Proto); IO.enumCase(Value, "TableGen", FormatStyle::LK_TableGen); IO.enumCase(Value, "TextProto", FormatStyle::LK_TextProto); - IO.enumCase(Value, "CSharp", FormatStyle::LK_CSharp); - IO.enumCase(Value, "Json", FormatStyle::LK_Json); } }; @@ -1501,6 +1502,9 @@ GoogleStyle.BreakStringLiterals = false; GoogleStyle.ColumnLimit = 100; GoogleStyle.NamespaceIndentation = FormatStyle::NI_All; + } else if (Language == FormatStyle::LK_Carbon) { + GoogleStyle.PointerAlignment = FormatStyle::PAS_Left; + GoogleStyle.ReflowComments = false; } return GoogleStyle; @@ -3487,6 +3491,8 @@ return FormatStyle::LK_TableGen; if (FileName.endswith_insensitive(".cs")) return FormatStyle::LK_CSharp; + if (FileName.endswith_insensitive(".carbon")) + return FormatStyle::LK_Carbon; if (FileName.endswith_insensitive(".json")) return FormatStyle::LK_Json; if (FileName.endswith_insensitive(".sv") || Index: clang/lib/Format/FormatToken.h =================================================================== --- clang/lib/Format/FormatToken.h +++ clang/lib/Format/FormatToken.h @@ -1143,6 +1143,11 @@ kw_with = &IdentTable.get("with"); kw_wor = &IdentTable.get("wor"); + // Carbon keywords + kw_me = &IdentTable.get("me"); + kw_fn = &IdentTable.get("fn"); + kw_match = &IdentTable.get("match"); + // Symbols that are treated as keywords. kw_verilogHash = &IdentTable.get("#"); kw_verilogHashHash = &IdentTable.get("##"); @@ -1172,6 +1177,9 @@ // Keywords from the Java section. kw_abstract, kw_extends, kw_implements, kw_instanceof, kw_interface}); + CarbonExtraKeywords = std::unordered_set( + {kw_me, kw_package, kw_fn, kw_abstract}); + // Some keywords are not included here because they don't need special // treatment like `showcancelled` or they should be treated as identifiers // like `int` and `logic`. @@ -1406,6 +1414,11 @@ IdentifierInfo *kw_when; IdentifierInfo *kw_where; + // Carbon keywords + IdentifierInfo *kw_me; + IdentifierInfo *kw_fn; + IdentifierInfo *kw_match; + // Verilog keywords IdentifierInfo *kw_always; IdentifierInfo *kw_always_comb; @@ -1800,6 +1813,9 @@ /// The Verilog keywords beyond the C++ keyword set. std::unordered_set VerilogExtraKeywords; + + /// The Carbon keywords beyond the C++ keyword set + std::unordered_set CarbonExtraKeywords; }; } // namespace format Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -617,7 +617,10 @@ Left->setType(TT_StructuredBindingLSquare); } else if (Left->is(TT_Unknown)) { if (StartsObjCMethodExpr) { - Left->setType(TT_ObjCMethodExpr); + if (Style.isCarbon()) + Left->setType(TT_JsTypeColon); + else + Left->setType(TT_ObjCMethodExpr); } else if (InsideInlineASM) { Left->setType(TT_InlineASMSymbolicNameLSquare); } else if (IsCpp11AttributeSpecifier) { @@ -765,7 +768,7 @@ // Remember that this is a [[using ns: foo]] C++ attribute, so we // don't add a space before the colon (unlike other colons). CurrentToken->setType(TT_AttributeColon); - } else if (!Style.isVerilog() && + } else if (!Style.isVerilog() && !Style.isCarbon() && Left->isOneOf(TT_ArraySubscriptLSquare, TT_DesignatedInitializerLSquare)) { Left->setType(TT_ObjCMethodExpr); @@ -775,6 +778,8 @@ // FIXME(bug 36976): ObjC return types shouldn't use TT_CastRParen. Parent->setType(TT_CastRParen); } + } else if (Style.isCarbon()) { + CurrentToken->setType(TT_JsTypeColon); } ColonFound = true; } @@ -983,7 +988,10 @@ } else if (Contexts.back().ColonIsDictLiteral || Style.Language == FormatStyle::LK_Proto || Style.Language == FormatStyle::LK_TextProto) { - Tok->setType(TT_DictLiteral); + if (Style.isCarbon()) + Tok->setType(TT_JsTypeColon); + else + Tok->setType(TT_DictLiteral); if (Style.Language == FormatStyle::LK_TextProto) { if (FormatToken *Previous = Tok->getPreviousNonComment()) Previous->setType(TT_SelectorName); @@ -1018,7 +1026,10 @@ ++Contexts.back().FirstObjCSelectorName->ObjCSelectorNameParts; } } else if (Contexts.back().ColonIsForRangeExpr) { - Tok->setType(TT_RangeBasedForLoopColon); + if (Style.isCarbon()) + Tok->setType(TT_JsTypeColon); + else + Tok->setType(TT_RangeBasedForLoopColon); } else if (CurrentToken && CurrentToken->is(tok::numeric_constant)) { Tok->setType(TT_BitFieldColon); } else if (Contexts.size() == 1 && @@ -1038,7 +1049,10 @@ if (PrevPrev && PrevPrev->isOneOf(tok::r_paren, tok::kw_noexcept)) Tok->setType(TT_CtorInitializerColon); } else { - Tok->setType(TT_InheritanceColon); + if (Style.isCarbon()) + Tok->setType(TT_JsTypeColon); + else + Tok->setType(TT_InheritanceColon); } } else if (canBeObjCSelectorComponent(*Tok->Previous) && Tok->Next && (Tok->Next->isOneOf(tok::r_paren, tok::comma) || @@ -1048,7 +1062,10 @@ // the colon are passed as macro arguments. Tok->setType(TT_ObjCMethodExpr); } else if (Contexts.back().ContextKind == tok::l_paren) { - Tok->setType(TT_InlineASMColon); + if (Style.isCarbon()) + Tok->setType(TT_JsTypeColon); + else + Tok->setType(TT_InlineASMColon); } break; case tok::pipe: @@ -1842,6 +1859,22 @@ } } + if (Style.isCarbon()) { + // -> T { + // -> T ; + // -> i32*; + if (Current.is(tok::arrow) && Current.Next->Next && + Current.Next->Next->isOneOf(TT_FunctionLBrace, tok::semi, + tok::star)) { + Current.setType(TT_TrailingReturnArrow); + } + // -> (Self,T) { + if (Current.is(tok::arrow) && Current.Next && + Current.Next->is(tok::l_paren)) { + Current.setType(TT_TrailingReturnArrow); + } + } + // Line.MightBeFunctionDecl can only be true after the parentheses of a // function declaration have been found. In this case, 'Current' is a // trailing token of this declaration and thus cannot be a name. @@ -2333,6 +2366,10 @@ const FormatToken *NextToken = Tok.getNextNonComment(); + // handle [addr me: Point*] + if (Style.isCarbon() && NextToken->is(tok::r_square)) + return TT_PointerOrReference; + if (InTemplateArgument && NextToken && NextToken->is(tok::kw_noexcept)) return TT_BinaryOperator; @@ -3323,6 +3360,15 @@ Right.MatchingParen->is(TT_CastRParen)) { return true; } + // (T:! Addable) + if (Style.isCarbon()) { + if (Left.is(TT_JsTypeColon) && Right.is(tok::exclaim)) + return false; + if (Left.is(tok::exclaim) && Left.Previous && + Left.Previous->is(TT_JsTypeColon)) { + return true; + } + } if (Style.isJson() && Left.is(tok::string_literal) && Right.is(tok::colon)) return false; if (Left.is(Keywords.kw_assert) && Style.Language == FormatStyle::LK_Java) @@ -3398,6 +3444,7 @@ tok::kw_true, tok::kw_false)) { return false; } + if (Left.is(tok::colon)) return !Left.is(TT_ObjCMethodExpr); if (Left.is(tok::coloncolon)) @@ -3504,6 +3551,8 @@ startsWithInitStatement(Line)))) { return false; } + if (Style.isCarbon() && Right.is(tok::r_square)) + return false; return Left.Previous && !Left.Previous->isOneOf( tok::l_paren, tok::coloncolon, tok::l_square); } @@ -3638,6 +3687,12 @@ return true; if (Left.is(tok::semi)) return true; + if (Style.isCarbon()) { + if (Left.isOneOf(Keywords.kw_match, Keywords.kw_var)) { + return Style.SpaceBeforeParensOptions.AfterControlStatements || + spaceRequiredBeforeParens(Right); + } + } if (Left.isOneOf(tok::pp_elif, tok::kw_for, tok::kw_while, tok::kw_switch, tok::kw_case, TT_ForEachMacro, TT_ObjCForIn) || Left.isIf(Line.Type != LT_PreprocessorDirective) || @@ -3908,6 +3963,9 @@ Right.is(tok::l_paren)) { return true; } + } else if (Style.isCarbon()) { + if (Right.is(TT_JsTypeColon)) + return false; } else if (Style.isJavaScript()) { if (Left.is(TT_FatArrow)) return true; @@ -4092,6 +4150,18 @@ return false; } + // (T:! Addable) + if (Style.isCarbon()) { + if (Left.is(tok::exclaim) && Left.Previous && + Left.Previous->is(TT_JsTypeColon)) { + return true; + } + if (Right.is(tok::l_paren) && Left.Previous && + Left.Previous->isOneOf(tok::kw_class, Keywords.kw_interface)) { + return false; + } + } + if (Right.isOneOf(TT_TrailingReturnArrow, TT_LambdaArrow) || Left.isOneOf(TT_TrailingReturnArrow, TT_LambdaArrow)) { return true; Index: clang/lib/Format/UnwrappedLineParser.cpp =================================================================== --- clang/lib/Format/UnwrappedLineParser.cpp +++ clang/lib/Format/UnwrappedLineParser.cpp @@ -1930,7 +1930,8 @@ tryToParseJSFunction(); break; } - if ((Style.isJavaScript() || Style.Language == FormatStyle::LK_Java) && + if ((Style.isJavaScript() || Style.Language == FormatStyle::LK_Java || + Style.isCarbon()) && FormatTok->is(Keywords.kw_interface)) { if (Style.isJavaScript()) { // In JavaScript/TypeScript, "interface" can be used as a standalone @@ -3667,9 +3668,9 @@ // parseRecord falls through and does not yet add an unwrapped line as a // record declaration or definition can start a structural element. parseRecord(); - // This does not apply to Java, JavaScript and C#. + // This does not apply to Java, JavaScript and C# or Carbon if (Style.Language == FormatStyle::LK_Java || Style.isJavaScript() || - Style.isCSharp()) { + Style.isCSharp() || Style.isCarbon()) { if (FormatTok->is(tok::semi)) nextToken(); addUnwrappedLine(); @@ -3825,6 +3826,10 @@ nextToken(); } } + if (Style.isCarbon()) { + if (FormatTok && FormatTok->is(tok::l_paren)) + parseParens(); + } } // Note that parsing away template declarations here leads to incorrectly Index: clang/tools/clang-format/ClangFormat.cpp =================================================================== --- clang/tools/clang-format/ClangFormat.cpp +++ clang/tools/clang-format/ClangFormat.cpp @@ -582,7 +582,8 @@ cl::SetVersionPrinter(PrintVersion); cl::ParseCommandLineOptions( argc, argv, - "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " + "A tool to format " + "C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C#/Carbon " "code.\n\n" "If no arguments are specified, it formats the code from standard input\n" "and writes the result to the standard output.\n" Index: clang/tools/clang-format/clang-format-diff.py =================================================================== --- clang/tools/clang-format/clang-format-diff.py +++ clang/tools/clang-format/clang-format-diff.py @@ -48,7 +48,7 @@ '(case sensitive, overrides -iregex)') parser.add_argument('-iregex', metavar='PATTERN', default= r'.*\.(cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp|hxx' - r'|m|mm|inc|js|ts|proto|protodevel|java|cs|json)', + r'|m|mm|inc|js|ts|proto|protodevel|java|cs|json|carbon)', help='custom pattern selecting file paths to reformat ' '(case insensitive, overridden by -regex)') parser.add_argument('-sort-includes', action='store_true', default=False, Index: clang/tools/clang-format/git-clang-format =================================================================== --- clang/tools/clang-format/git-clang-format +++ clang/tools/clang-format/git-clang-format @@ -98,6 +98,7 @@ 'ts', # TypeScript 'cs', # C Sharp 'json', # Json + 'carbon', # Carbon ]) p = argparse.ArgumentParser( Index: clang/unittests/Format/CMakeLists.txt =================================================================== --- clang/unittests/Format/CMakeLists.txt +++ clang/unittests/Format/CMakeLists.txt @@ -6,6 +6,7 @@ CleanupTest.cpp DefinitionBlockSeparatorTest.cpp FormatTest.cpp + FormatTestCarbon.cpp FormatTestComments.cpp FormatTestCSharp.cpp FormatTestJS.cpp Index: clang/unittests/Format/FormatTestCarbon.cpp =================================================================== --- /dev/null +++ clang/unittests/Format/FormatTestCarbon.cpp @@ -0,0 +1,139 @@ +//===- unittest/Format/FormatTestCarbon.cpp - Formatting tests for Carbon -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "FormatTestUtils.h" +#include "clang/Format/Format.h" +#include "llvm/Support/Debug.h" +#include "gtest/gtest.h" + +#define DEBUG_TYPE "format-test-json" + +namespace clang { +namespace format { + +class FormatTestCarbon : public ::testing::Test { +protected: + static std::string format(llvm::StringRef Code, unsigned Offset, + unsigned Length, const FormatStyle &Style) { + LLVM_DEBUG(llvm::errs() << "---\n"); + LLVM_DEBUG(llvm::errs() << Code << "\n\n"); + std::vector Ranges(1, tooling::Range(Offset, Length)); + tooling::Replacements Replaces = reformat(Style, Code, Ranges); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast(Result)); + LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); + return *Result; + } + + static std::string + format(llvm::StringRef Code, + const FormatStyle &Style = getGoogleStyle(FormatStyle::LK_Carbon)) { + return format(Code, 0, Code.size(), Style); + } + + static FormatStyle getStyleWithColumns(unsigned ColumnLimit) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + Style.ColumnLimit = ColumnLimit; + return Style; + } + + static void verifyFormatStable(llvm::StringRef Code, + const FormatStyle &Style) { + EXPECT_EQ(Code.str(), format(Code, Style)) << "Expected code is not stable"; + } + + static void verifyFormat( + llvm::StringRef Code, + const FormatStyle &Style = getGoogleStyle(FormatStyle::LK_Carbon)) { + verifyFormatStable(Code, Style); + EXPECT_EQ(Code.str(), format(test::messUp(Code), Style)); + } +}; + +TEST_F(FormatTestCarbon, CarbonBasicFunctions) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + verifyFormat("fn Sum(var a: i32, var b: i32) -> i32 { return a + b; }", + Style); + verifyFormat("// Empty or void return type.\n" + "fn PrintCount(var count: i32) { Print(\" The count is { 0 } " + "\", count); }", + Style); +} + +TEST_F(FormatTestCarbon, CarbonFunctionPrototype) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("fn GetSetX[addr me: Point*](x: i32) -> i32 {", Style); + verifyFormat("fn Add[me: Self](k: i32) -> Self;", Style); + verifyFormat("fn Add[me: Self](k: i32) -> Self {", Style); + verifyFormat("fn Main() -> i32 {", Style); + verifyFormat("fn Make() -> (Self, T);", Style); + verifyFormat("fn Make() -> (Self, T) {", Style); + verifyFormat("fn F(n: i32, p: i32*, q: i32***) -> i32* {", Style); +} + +TEST_F(FormatTestCarbon, CarbonVariable) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("var name: auto = \"Carbon is an open-source programming\";", + Style); + verifyFormat("let id: i32 = 57;", Style); + verifyFormat("var total_articles_published: i32 = 4500;", Style); +} + +TEST_F(FormatTestCarbon, CarbonVariableInLoop) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("for (var name: String in names) {", Style); +} + +TEST_F(FormatTestCarbon, CarbonVariableBreakAfterClass) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("class Sum {\n" + " var a: i32;\n" + "}\n" + "fn Main() -> i32 { var p1: Sum = {.a = 5}; }", + Style); +} + +TEST_F(FormatTestCarbon, CarbonClassAndInterface) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("class Class {\n" + " var a: i32;\n" + "}", + Style); + verifyFormat("class GenericClass(T:! Type) {\n" + " var a: T;\n" + "}", + Style); + verifyFormat("interface Interface {\n" + " fn Make() -> Self;\n" + "}", + Style); + verifyFormat("interface GenericInterface(T:! Type) {\n" + " fn Make() -> Self;\n" + "}", + Style); +} + +TEST_F(FormatTestCarbon, CarbonMatch) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("match (x) {", Style); +} + +TEST_F(FormatTestCarbon, CarbonTuple) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + verifyFormat("var (x: auto, y: auto) = (2, 3);", Style); +} + +} // namespace format +} // end namespace clang Index: clang/unittests/Format/TokenAnnotatorTest.cpp =================================================================== --- clang/unittests/Format/TokenAnnotatorTest.cpp +++ clang/unittests/Format/TokenAnnotatorTest.cpp @@ -1045,6 +1045,97 @@ EXPECT_TOKEN(Tokens[9], tok::colon, TT_GotoLabelColon); } +TEST_F(TokenAnnotatorTest, UnderstandsCarbonReturnType) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("fn Sum(var a: i32, var b: i32) -> i32 {", Style); + ASSERT_EQ(Tokens.size(), 17u) << Tokens; + EXPECT_TOKEN(Tokens[13], tok::arrow, TT_TrailingReturnArrow); + EXPECT_TOKEN(Tokens[15], tok::l_brace, TT_FunctionLBrace); + + Tokens = annotate("fn Add[me: Self](k: i32) -> Self {", Style); + ASSERT_EQ(Tokens.size(), 16u) << Tokens; + EXPECT_TOKEN(Tokens[12], tok::arrow, TT_TrailingReturnArrow); + EXPECT_TOKEN(Tokens[14], tok::l_brace, TT_FunctionLBrace); + + Tokens = annotate("fn Add[me: Self](k: i32) -> Self;", Style); + ASSERT_EQ(Tokens.size(), 16u) << Tokens; + EXPECT_TOKEN(Tokens[12], tok::arrow, TT_TrailingReturnArrow); + EXPECT_TOKEN(Tokens[14], tok::semi, TT_Unknown); + + Tokens = annotate("fn Add[me: Self](k: i32) -> i32*;", Style); + ASSERT_EQ(Tokens.size(), 17u) << Tokens; + EXPECT_TOKEN(Tokens[12], tok::arrow, TT_TrailingReturnArrow); + EXPECT_TOKEN(Tokens[15], tok::semi, TT_Unknown); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonTypeOnColon) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("let id: i32 = 57;", Style); + ASSERT_EQ(Tokens.size(), 8u) << Tokens; + EXPECT_TOKEN(Tokens[2], tok::colon, TT_JsTypeColon); + + Tokens = annotate("var t2: {.x: i32, .y: i32} = {.x = 2, .y = 5};", Style); + ASSERT_EQ(Tokens.size(), 28u) << Tokens; + EXPECT_TOKEN(Tokens[6], tok::colon, TT_JsTypeColon); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonPointerTypeOnColon) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + auto Tokens = annotate("fn GetSetX[addr me: Point*](x: i32) -> i32 {", Style); + ASSERT_EQ(Tokens.size(), 18u) << Tokens; + EXPECT_TOKEN(Tokens[5], tok::colon, TT_JsTypeColon); + EXPECT_TOKEN(Tokens[7], tok::star, TT_PointerOrReference); + + Tokens = annotate( + "fn GetSetX[addr me: Point*, addr me: Point*](x: i32) -> i32 {", Style); + ASSERT_EQ(Tokens.size(), 24u) << Tokens; + EXPECT_TOKEN(Tokens[5], tok::colon, TT_JsTypeColon); + EXPECT_TOKEN(Tokens[7], tok::star, TT_PointerOrReference); + EXPECT_TOKEN(Tokens[11], tok::colon, TT_JsTypeColon); + EXPECT_TOKEN(Tokens[13], tok::star, TT_PointerOrReference); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonTypeOnColonForLoop) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("for (var name: String in names) {", Style); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[4], tok::colon, TT_JsTypeColon); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonTypeExclaim) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("(T:! Addable)", Style); + ASSERT_EQ(Tokens.size(), 7u) << Tokens; + EXPECT_TOKEN(Tokens[2], tok::colon, TT_JsTypeColon); + EXPECT_TOKEN(Tokens[3], tok::exclaim, TT_UnaryOperator); + ASSERT_EQ(Tokens[3]->SpacesRequiredBefore, false); + EXPECT_TOKEN(Tokens[4], tok::identifier, TT_Unknown); + ASSERT_EQ(Tokens[4]->SpacesRequiredBefore, true); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonClassTemplate) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("class GenericClass(T:! Type) {", Style); + ASSERT_EQ(Tokens.size(), 10u) << Tokens; + EXPECT_TOKEN(Tokens[2], tok::l_paren, TT_Unknown); + ASSERT_EQ(Tokens[2]->SpacesRequiredBefore, false); + ASSERT_EQ(Tokens[2]->MustBreakBefore, false); +} + +TEST_F(TokenAnnotatorTest, UnderstandsCarbonMatch) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Carbon); + + auto Tokens = annotate("match (x)", Style); + ASSERT_EQ(Tokens.size(), 5u) << Tokens; + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_Unknown); + ASSERT_EQ(Tokens[1]->SpacesRequiredBefore, true); +} + } // namespace } // namespace format } // namespace clang