Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4264,6 +4264,14 @@ ERROR(attr_MainType_without_main,none,
"%0 is annotated with '@main' and must provide a main static function "
"of type %" SELECT_APPLICATION_MAIN_TYPES "1",
(const ValueDecl *, bool))
NOTE(note_add_main_sync,none,
"add 'static func main()'", ())
NOTE(note_add_main_sync_throws,none,
"add 'static func main() throws'", ())
NOTE(note_add_main_async,none,
"add 'static func main() async'", ())
NOTE(note_add_main_async_throws,none,
"add 'static func main() async throws'", ())

#undef SELECT_APPLICATION_MAIN_TYPES
#undef SELECT_APPLICATION_MAIN
Expand Down
71 changes: 68 additions & 3 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "TypeCheckObjC.h"
#include "TypeCheckType.h"
#include "TypeChecker.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/ClangModuleLoader.h"
Expand Down Expand Up @@ -3081,6 +3082,36 @@ synthesizeMainBody(AbstractFunctionDecl *fn, void *arg) {
return std::make_pair(body, /*typechecked=*/false);
}

static llvm::SmallString<128>
generateMainFunctionText(ASTContext &C, NominalTypeDecl *parentDecl,
bool isThrows, bool isAsync) {
StringRef ExtraIndent;
StringRef CurrentIndent = Lexer::getIndentationForLine(
C.SourceMgr, parentDecl->getStartLoc(), &ExtraIndent);
std::string MethodIndent = (CurrentIndent + ExtraIndent).str();

llvm::SmallString<128> Text;
llvm::raw_svector_ostream OS(Text);
ExtraIndentStreamPrinter Printer(OS, MethodIndent);

Printer.printNewline();

Printer << "static func main() ";
if (isAsync)
Printer << "async ";
if (isThrows)
Printer << "throws ";

// Print the "{ <#code#> }" placeholder body.
Printer << "{\n";
Printer.printIndent();
Printer << ExtraIndent << getCodePlaceholder();
Printer.printNewline();
Printer << "}\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Should we add two newlines here to separate the generated main function from the remaining body using a newline?

Copy link
Collaborator

@AnthonyLatsis AnthonyLatsis Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t have a clear preference. Ideally (not in this PR!), I imagine a formatCodeForInsertion function that parses a given string into a SwiftSyntax tree and formats it using SwiftBasicFormat (indentation, separation) for insertion at a given location.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way that Fix-Its and refactorings are designed to work is that the editor applies its own indentation after applying the Fix-It refactoring because it can really know about the user’s preferences (the compiler can only guess based on existing lines). But whether to add a newline is currently an opinionated decision by the compiler.

Copy link
Contributor Author

@CrazyFanFan CrazyFanFan Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the discussion.

I've looked at other fix-it instances in TypeCheckProtocol.cpp and TypeCheckDistributed.cpp, and found that they only add a single \n at the end of inserted code, without additional blank lines.

Considering this, and editors applying indentation based on user preferences, I'm inclined to stay consistent with existing practices and not insert extra blank lines.

Of course, if you have any other thoughts, I'm open to further discussion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding those examples. In that case let’s stick with the existing practice of only adding one newline. 👍🏽


return Text;
}

FuncDecl *
SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
Decl *D) const {
Expand Down Expand Up @@ -3210,9 +3241,43 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
const bool hasAsyncSupport =
AvailabilityRange::forDeploymentTarget(context).isContainedIn(
context.getBackDeployedConcurrencyAvailability());
context.Diags.diagnose(attr->getLocation(),
diag::attr_MainType_without_main,
nominal, hasAsyncSupport);

auto location = attr->getLocation();
auto fixLocation = braces.Start;

context.Diags.diagnose(location, diag::attr_MainType_without_main, nominal,
hasAsyncSupport);

// Offer fix-its to add the `main` function for different combinations of
// effects, starting with no effects.

context.Diags.diagnose(location, diag::note_add_main_sync)
.fixItInsertAfter(fixLocation, generateMainFunctionText(
context, nominal, /*isThrows*/ false,
/*isAsync*/ false)
.str());

context.Diags.diagnose(location, diag::note_add_main_sync_throws)
.fixItInsertAfter(fixLocation, generateMainFunctionText(
context, nominal, /*isThrows*/ true,
/*isAsync*/ false)
.str());

if (hasAsyncSupport) {
context.Diags.diagnose(location, diag::note_add_main_async)
.fixItInsertAfter(fixLocation,
generateMainFunctionText(context, nominal,
/*isThrows*/ false,
/*isAsync*/ true)
.str());

context.Diags.diagnose(location, diag::note_add_main_async_throws)
.fixItInsertAfter(fixLocation,
generateMainFunctionText(context, nominal,
/*isThrows*/ true,
/*isAsync*/ true)
.str());
}
attr->setInvalid();
return nullptr;
}
Expand Down
5 changes: 4 additions & 1 deletion test/attr/ApplicationMain/attr_main_arguments.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -verify %s

@main // expected-error{{'MyBase' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{8:16-16=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{8:16-16=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{8:16-16=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{8:16-16=\n static func main() async throws {\n <#code#>\n }\n}}
struct MyBase {
static func main(_ argc: Int, _ argv: [String]) {
}
}

4 changes: 4 additions & 0 deletions test/attr/ApplicationMain/attr_main_dynamicCallable.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -verify %s

@main // expected-error{{'Foo' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{8:13-13=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{8:13-13=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{8:13-13=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{8:13-13=\n static func main() async throws {\n <#code#>\n }\n}}
struct Foo {
@dynamicCallable
struct main {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -verify %s

@main @dynamicMemberLookup // expected-error{{'Main' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{8:14-14=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{8:14-14=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{8:14-14=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{8:14-14=\n static func main() async throws {\n <#code#>\n }\n}}
struct Main {
subscript(dynamicMember member: String) -> () -> Void {
return {
}
}
}

6 changes: 4 additions & 2 deletions test/attr/ApplicationMain/attr_main_extension_nofunc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ class EntryPoint {
}

@main // expected-error{{'EntryPoint' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{11:23-23=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{11:23-23=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{11:23-23=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{11:23-23=\n static func main() async throws {\n <#code#>\n }\n}}
extension EntryPoint {
}


15 changes: 15 additions & 0 deletions test/attr/ApplicationMain/attr_main_instance.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -verify %s

@main // expected-error{{'MyBase' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{8:15-15=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{8:15-15=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{8:15-15=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{8:15-15=\n static func main() async throws {\n <#code#>\n }\n}}
class MyBase {
func main() {
}
}

enum Nested {
@main // expected-error{{'MyBase' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{19:17-17=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{19:17-17=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{19:17-17=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{19:17-17=\n static func main() async throws {\n <#code#>\n }\n}}
class MyBase {
func main() {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -target %target-swift-5.0-abi-triple -verify %s
// REQUIRES: OS=macosx && CPU=x86_64

@main // expected-error{{'MyBaseWithoutAsyncSupport' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{7:34-34=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{7:34-34=\n static func main() throws {\n <#code#>\n }\n}}
class MyBaseWithoutAsyncSupport {
func main() {
}
}
5 changes: 4 additions & 1 deletion test/attr/ApplicationMain/attr_main_return.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// RUN: %target-swift-frontend -typecheck -parse-as-library -verify %s

@main // expected-error{{'MyBase' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{8:16-16=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{8:16-16=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{8:16-16=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{8:16-16=\n static func main() async throws {\n <#code#>\n }\n}}
struct MyBase {
static func main() -> Int {
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ extension Runnable where Self : OtherThing {
}

@main //expected-error{{'EntryPoint' is annotated with '@main' and must provide a main static function}}
// expected-note@-1{{add 'static func main()'}} {{23:31-31=\n static func main() {\n <#code#>\n }\n}}
// expected-note@-2{{add 'static func main() throws'}} {{23:31-31=\n static func main() throws {\n <#code#>\n }\n}}
// expected-note@-3{{add 'static func main() async'}} {{23:31-31=\n static func main() async {\n <#code#>\n }\n}}
// expected-note@-4{{add 'static func main() async throws'}} {{23:31-31=\n static func main() async throws {\n <#code#>\n }\n}}
struct EntryPoint : Runnable {
func run() {
}
}