diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 43e4f6796b6dd..90c9466fdf431 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1399,11 +1399,7 @@ class AnnotatingParser { } break; } - if (Line.First->isOneOf(Keywords.kw_module, Keywords.kw_import) || - Line.First->startsSequence(tok::kw_export, Keywords.kw_module) || - Line.First->startsSequence(tok::kw_export, Keywords.kw_import)) { - Tok->setType(TT_ModulePartitionColon); - } else if (Line.First->is(tok::kw_asm)) { + if (Line.First->is(tok::kw_asm)) { Tok->setType(TT_InlineASMColon); } else if (Contexts.back().ColonIsDictLiteral || Style.isProto()) { Tok->setType(TT_DictLiteral); @@ -4314,7 +4310,8 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const { } Current->CanBreakBefore = - Current->MustBreakBefore || canBreakBefore(Line, *Current); + !Line.IsModuleOrImportDecl && + (Current->MustBreakBefore || canBreakBefore(Line, *Current)); if (Current->is(TT_FunctionDeclarationLParen)) { InParameterList = true; diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h index 52d6e5ca56915..264f39b7b1d60 100644 --- a/clang/lib/Format/TokenAnnotator.h +++ b/clang/lib/Format/TokenAnnotator.h @@ -58,6 +58,7 @@ class AnnotatedLine { InPPDirective(Line.InPPDirective), InPragmaDirective(Line.InPragmaDirective), InMacroBody(Line.InMacroBody), + IsModuleOrImportDecl(Line.IsModuleOrImportDecl), MustBeDeclaration(Line.MustBeDeclaration), MightBeFunctionDecl(false), IsMultiVariableDeclStmt(false), Affected(false), LeadingEmptyLinesAffected(false), ChildrenAffected(false), @@ -184,6 +185,7 @@ class AnnotatedLine { bool InPPDirective; bool InPragmaDirective; bool InMacroBody; + bool IsModuleOrImportDecl; bool MustBeDeclaration; bool MightBeFunctionDecl; bool IsMultiVariableDeclStmt; diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 9536a233def58..5a73e7d9d015d 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -108,6 +108,7 @@ class ScopedLineState { Parser.Line->PPLevel = PreBlockLine->PPLevel; Parser.Line->InPPDirective = PreBlockLine->InPPDirective; Parser.Line->InMacroBody = PreBlockLine->InMacroBody; + Parser.Line->IsModuleOrImportDecl = PreBlockLine->IsModuleOrImportDecl; Parser.Line->UnbracedBodyLevel = PreBlockLine->UnbracedBodyLevel; } @@ -1350,40 +1351,66 @@ static bool isC78ParameterDecl(const FormatToken *Tok, const FormatToken *Next, return Tok->Previous && Tok->Previous->isOneOf(tok::l_paren, tok::comma); } -bool UnwrappedLineParser::parseModuleImport() { - assert(FormatTok->is(Keywords.kw_import) && "'import' expected"); +bool UnwrappedLineParser::parseModuleDecl() { + assert(IsCpp); + assert(FormatTok->is(Keywords.kw_module)); - if (auto Token = Tokens->peekNextToken(/*SkipComment=*/true); - !Token->Tok.getIdentifierInfo() && - Token->isNoneOf(tok::colon, tok::less, tok::string_literal)) { + if (Style.Language == FormatStyle::LK_C || + Style.Standard < FormatStyle::LS_Cpp20) { return false; } nextToken(); - while (!eof()) { - if (FormatTok->is(tok::colon)) { + if (FormatTok->isNot(tok::identifier)) + return false; + + for (nextToken(); FormatTok->isNoneOf(tok::semi, tok::eof); nextToken()) + if (FormatTok->is(tok::colon)) FormatTok->setFinalizedType(TT_ModulePartitionColon); - } + + nextToken(); + Line->IsModuleOrImportDecl = true; + addUnwrappedLine(); + return true; +} + +bool UnwrappedLineParser::parseImportDecl() { + assert(IsCpp); + assert(FormatTok->is(Keywords.kw_import) && "'import' expected"); + + if (Style.Language == FormatStyle::LK_C || + Style.Standard < FormatStyle::LS_Cpp20) { + return false; + } + + nextToken(); + if (FormatTok->is(tok::colon)) { + FormatTok->setFinalizedType(TT_ModulePartitionColon); + nextToken(); + } + if (FormatTok->isNoneOf(tok::identifier, tok::less, tok::string_literal)) + return false; + + for (;; nextToken()) { // Handle import as we would an include statement. - else if (FormatTok->is(tok::less)) { - nextToken(); - while (FormatTok->isNoneOf(tok::semi, tok::greater) && !eof()) { - // Mark tokens up to the trailing line comments as implicit string - // literals. - if (FormatTok->isNot(tok::comment) && - !FormatTok->TokenText.starts_with("//")) { - FormatTok->setFinalizedType(TT_ImplicitStringLiteral); + if (FormatTok->is(tok::less)) { + for (nextToken(); FormatTok->isNoneOf(tok::semi, tok::eof); nextToken()) { + if (FormatTok->is(tok::greater)) { + nextToken(); + break; } - nextToken(); + // Mark tokens as implicit string literals, so that import will + // neither be broken nor have a space added. + FormatTok->setFinalizedType(TT_ImplicitStringLiteral); } } - if (FormatTok->is(tok::semi)) { + if (FormatTok->isOneOf(tok::semi, tok::eof)) { nextToken(); break; } - nextToken(); } + Line->IsModuleOrImportDecl = true; addUnwrappedLine(); return true; } @@ -1624,14 +1651,6 @@ void UnwrappedLineParser::parseStructuralElement( } break; case tok::kw_export: - if (Style.isJavaScript()) { - parseJavaScriptEs6ImportExport(); - return; - } - if (Style.isVerilog()) { - parseVerilogExtern(); - return; - } if (IsCpp) { nextToken(); if (FormatTok->is(tok::kw_namespace)) { @@ -1642,8 +1661,19 @@ void UnwrappedLineParser::parseStructuralElement( parseCppExportBlock(); return; } - if (FormatTok->is(Keywords.kw_import) && parseModuleImport()) + if (FormatTok->is(Keywords.kw_module) && parseModuleDecl()) + return; + if (FormatTok->is(Keywords.kw_import) && parseImportDecl()) return; + break; + } + if (Style.isJavaScript()) { + parseJavaScriptEs6ImportExport(); + return; + } + if (Style.isVerilog()) { + parseVerilogExtern(); + return; } break; case tok::kw_inline: @@ -1664,6 +1694,8 @@ void UnwrappedLineParser::parseStructuralElement( return; } if (FormatTok->is(Keywords.kw_import)) { + if (IsCpp && parseImportDecl()) + return; if (Style.isJavaScript()) { parseJavaScriptEs6ImportExport(); return; @@ -1684,25 +1716,27 @@ void UnwrappedLineParser::parseStructuralElement( parseVerilogExtern(); return; } - if (IsCpp && parseModuleImport()) - return; } - if (IsCpp && FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals, - Keywords.kw_slots, Keywords.kw_qslots)) { - nextToken(); - if (FormatTok->is(tok::colon)) { + if (IsCpp) { + if (FormatTok->is(Keywords.kw_module) && parseModuleDecl()) + return; + if (FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals, + Keywords.kw_slots, Keywords.kw_qslots)) { nextToken(); - addUnwrappedLine(); + if (FormatTok->is(tok::colon)) { + nextToken(); + addUnwrappedLine(); + return; + } + } + if (FormatTok->is(TT_StatementMacro)) { + parseStatementMacro(); + return; + } + if (FormatTok->is(TT_NamespaceMacro)) { + parseNamespace(); return; } - } - if (IsCpp && FormatTok->is(TT_StatementMacro)) { - parseStatementMacro(); - return; - } - if (IsCpp && FormatTok->is(TT_NamespaceMacro)) { - parseNamespace(); - return; } // In Verilog labels can be any expression, so we don't do them here. // JS doesn't have macros, and within classes colons indicate fields, not diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h index 8181eec1495f7..bc049f5669f6a 100644 --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -48,6 +48,8 @@ struct UnwrappedLine { bool InPragmaDirective = false; /// Whether it is part of a macro body. bool InMacroBody = false; + /// Whether it is a C++20 module/import declaration. + bool IsModuleOrImportDecl = false; /// Nesting level of unbraced body of a control statement. unsigned UnbracedBodyLevel = 0; @@ -164,7 +166,8 @@ class UnwrappedLineParser { void parseCaseLabel(); void parseSwitch(bool IsExpr); void parseNamespace(); - bool parseModuleImport(); + bool parseModuleDecl(); + bool parseImportDecl(); void parseNew(); void parseAccessSpecifier(); bool parseEnum(); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 83e2c5b38ceaf..3c22f4fe8866c 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -14279,18 +14279,16 @@ TEST_F(FormatTest, HandlesIncludeDirectives) { verifyFormat("#define F __has_include_next()"); // Protocol buffer definition or missing "#". - verifyFormat("import \"aaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaa\";", - getLLVMStyleWithColumns(30)); + constexpr StringRef Code("import \"aaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaa\";"); + auto Style = getLLVMStyleWithColumns(30); + verifyFormat(Code, Style); + Style.Language = FormatStyle::LK_Proto; + verifyFormat(Code, Style); - FormatStyle Style = getLLVMStyle(); + Style.Language = FormatStyle::LK_Cpp; Style.AlwaysBreakBeforeMultilineStrings = true; Style.ColumnLimit = 0; verifyFormat("#import \"abc.h\"", Style); - - // But 'import' might also be a regular C++ namespace. - verifyFormat("import::SomeFunction(aaaaaaaaaaaaaaaaaaaaaaaaaaa,\n" - " aaaaaaaaaaaaaaaaaaaaaaaaaaaaa);"); - verifyFormat("import::Bar foo(val ? 2 : 1);"); } //===----------------------------------------------------------------------===// @@ -24791,9 +24789,7 @@ TEST_F(FormatTest, Cpp20ModulesSupport) { Style.AllowShortFunctionsOnASingleLine = FormatStyle::ShortFunctionStyle(); verifyFormat("export import foo;", Style); - verifyFormat("export import foo:bar;", Style); verifyFormat("export import foo.bar;", Style); - verifyFormat("export import foo.bar:baz;", Style); verifyFormat("export import :bar;", Style); verifyFormat("export module foo:bar;", Style); verifyFormat("export module foo;", Style); @@ -24821,7 +24817,6 @@ TEST_F(FormatTest, Cpp20ModulesSupport) { verifyFormat("import bar;", Style); verifyFormat("import foo.bar;", Style); - verifyFormat("import foo:bar;", Style); verifyFormat("import :bar;", Style); verifyFormat("import /* module partition */ :bar;", Style); verifyFormat("import ;", Style); @@ -24837,11 +24832,10 @@ TEST_F(FormatTest, Cpp20ModulesSupport) { "}", Style); - verifyFormat("module :private;", Style); + verifyFormat("module : private;", Style); verifyFormat("import ;", Style); verifyFormat("import foo...bar;", Style); verifyFormat("import ..........;", Style); - verifyFormat("module foo:private;", Style); verifyFormat("import a", Style); verifyFormat("module a", Style); verifyFormat("export import a", Style); @@ -24851,8 +24845,25 @@ TEST_F(FormatTest, Cpp20ModulesSupport) { verifyFormat("module", Style); verifyFormat("export", Style); + verifyFormat("import /* comment */;", Style); + verifyFormat("import ; // Trailing comment", Style); + + Style.ColumnLimit = 10; + verifyFormat("import Foo.Bar;", Style); + verifyFormat("export import Foo.Bar;", Style); + verifyFormat("export module Foo.Bar;", Style); + verifyFormat("export module Foo.Bar:Baz;", Style); + verifyFormat("import /* comment */;", Style); + verifyFormat("import ; // Trailing comment", Style); + + // Somewhat gracefully handle import in pre-C++20 code. verifyFormat("import /* not keyword */ = val ? 2 : 1;"); verifyFormat("_world->import();"); + + // But 'import' might also be a regular C++ namespace. + verifyFormat("import::SomeFunction(aaaaaaaaaaaaaaaaaaaaaaaaaaa,\n" + " aaaaaaaaaaaaaaaaaaaaaaaaaaaaa);"); + verifyFormat("import::Bar foo(val ? 2 : 1);"); } TEST_F(FormatTest, CoroutineForCoawait) {