Swift Module 1 – Getting started: Swift 6.4, toolchains and your first program
Compiler errors, language modes and feature flags
Read a Swift diagnostic part by part, switch between the Swift 5 and Swift 6 language modes, turn on upcoming features early and control warnings by group.
What you will learn
- Read a Swift diagnostic part by part, from its location and severity to its notes, fix-its and diagnostic group
- Explain the Swift 5 and Swift 6 language modes and how a flag, a package setting or the tools version selects one
- Configure upcoming features with -enable-upcoming-feature, its migration mode and Package.swift settings
- Configure what warnings become with -Werror, -Wwarning, -warnings-as-errors and the @diagnose attribute
Before you start
What you’ll learn · Before you start
What you will learn
- Read a Swift diagnostic part by part, from its location and severity to its notes, fix-its and diagnostic group
- Explain the Swift 5 and Swift 6 language modes and how a flag, a package setting or the tools version selects one
- Configure upcoming features with -enable-upcoming-feature, its migration mode and Package.swift settings
- Configure what warnings become with -Werror, -Wwarning, -warnings-as-errors and the @diagnose attribute
Before you start
On this page
The 17-line file at the start of this lesson makes the compiler print 40 lines: the place of each problem, the lines around it, notes that point elsewhere and suggested fixes. And the same file can build cleanly or fail depending on its language mode: The set of language rules the compiler applies to your code: Swift 6, 5, 4.2 or 4. One compiler supports several modes, chosen with -language-mode or a package setting; the Swift 6 mode reports possible data races as errors.: a global counter that the Swift 5 mode accepts is an error in the Swift 6 mode.
This lesson reads a diagnostic: A message from the compiler about the code: an error, which stops the build, a warning, which does not, or a note that adds detail. Each starts with the file, the line and the column. part by part, switches between the two language modes and
shows what decides the mode of a build. Then it turns on rules of the next language mode early, one feature at a time,
and decides which warnings must stop a build, with flags on the command line, settings in Package.swift and an
attribute in the code.
A diagnostic names the place, the problem and a fix
This file has one problem that is an error in the Swift 6 language mode and three that are warnings:
// stall.swift: one error and three warnings, each with its diagnostic group.
struct Stall {
static var openStalls = 10
}
@available(*, deprecated, renamed: "price(cups:)")
func oldPrice(cups: Int) -> Int { cups * 25 }
func price(cups: Int) -> Int { cups * 25 }
func order() {
var cups = 3
let note = "no sugar"
print(oldPrice(cups: cups))
}
order() Compiled in the Swift 6 mode, every diagnostic comes with the lines around its place:
from session import run, version
# stall.swift in the Swift 6 language mode: one error and three warnings. The build is meant to fail.
version()
run("""
!swiftc -language-mode 6 stall.swift -o stall
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 stall.swift -o stall
stall.swift:3:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
1 | // stall.swift: one error and three warnings, each with its diagnostic group.
2 | struct Stall {
3 | static var openStalls = 10
| |- error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
| |- note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
| |- note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
| `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
4 | }
5 |
stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
12 | var cups = 3
13 | let note = "no sugar"
14 | print(oldPrice(cups: cups))
| |- warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
| `- note: use 'price(cups:)' instead
15 | }
16 |
stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
10 |
11 | func order() {
12 | var cups = 3
| `- warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
13 | let note = "no sugar"
14 | print(oldPrice(cups: cups))
stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
11 | func order() {
12 | var cups = 3
13 | let note = "no sugar"
| `- warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
14 | print(oldPrice(cups: cups))
15 | }
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[#MutableGlobalVariable]: <https://docs.swift.org/compiler/documentation/diagnostics/mutable-global-variable>
[#NoUsage]: <https://docs.swift.org/compiler/documentation/diagnostics/no-usage>
[#VariableNeverMutated]: <https://docs.swift.org/compiler/documentation/diagnostics/variable-never-mutated>
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 diagnostics_session.py
Each diagnostic has the same parts, and each part tells you something different:
| Part | In the session | What it tells you |
|---|---|---|
| Location | stall.swift:3:16 |
The file, the line and the column |
| Severity | error, warning, note |
An error stops the build; a warning and a note do not |
| Message | static property 'openStalls' is not … |
What is wrong, in words |
| Group | [#MutableGlobalVariable] |
A stable name with its own page on docs.swift.org |
| Notes | convert 'openStalls' to a 'let' constant … |
Ways to fix it, or other places involved |
| Fix-it | let, @MainActor, _ (llvm style, below) |
The exact edit an editor offers to make |
The diagnostic group: A stable name for a family of diagnostics, printed in brackets after the message, such as [#DeprecatedDeclaration]. Flags such as -Werror and the @diagnose attribute act on groups, and docs.swift.org explains each one. at the end of a message is the part to search for: the compiler lists each group’s page under its output, and the page explains the rule with examples. The fix-its are easier to see in the compact llvm style, which prints each one under the code, and the second build shows that the Swift 5 mode reports only the three warnings and writes the program:
from session import run, version
# The same file in the compact llvm style, which prints each fix-it under the code (meant to fail), then in the
# Swift 5 language mode, where only the warnings are left and the build succeeds.
version()
run("""
!swiftc -language-mode 6 -diagnostic-style llvm stall.swift -o stall
swiftc -language-mode 5 stall.swift -o stall
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 -diagnostic-style llvm stall.swift -o stall
stall.swift:3:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
static var openStalls = 10
^
stall.swift:3:16: note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
static var openStalls = 10
~~~ ^
let
stall.swift:3:16: note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
static var openStalls = 10
^
@MainActor
stall.swift:3:16: note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
static var openStalls = 10
^
nonisolated(unsafe)
stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
print(oldPrice(cups: cups))
^
stall.swift:14:11: note: use 'price(cups:)' instead
print(oldPrice(cups: cups))
^~~~~~~~
price
stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
var cups = 3
~~~ ^
let
stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
let note = "no sugar"
~~~~^~~~
_
[exit status 1]
$ swiftc -language-mode 5 stall.swift -o stall
stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
12 | var cups = 3
13 | let note = "no sugar"
14 | print(oldPrice(cups: cups))
| |- warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
| `- note: use 'price(cups:)' instead
15 | }
16 |
stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
10 |
11 | func order() {
12 | var cups = 3
| `- warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
13 | let note = "no sugar"
14 | print(oldPrice(cups: cups))
stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
11 | func order() {
12 | var cups = 3
13 | let note = "no sugar"
| `- warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
14 | print(oldPrice(cups: cups))
15 | }
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[#NoUsage]: <https://docs.swift.org/compiler/documentation/diagnostics/no-usage>
[#VariableNeverMutated]: <https://docs.swift.org/compiler/documentation/diagnostics/variable-never-mutated>
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 styles_session.py
Under each caret line, the llvm style prints the text of a fix-it: A ready-made edit attached to a diagnostic, such as replacing var with let. Editors offer to apply it, and -diagnostic-style llvm prints it under the code.: let in place of var,
@MainActor before the property, price in place of oldPrice, _ in place of the unused name. An editor offers the
same edits as buttons. When a build reports many errors, fix the first one first: later errors are often caused by the
earlier ones, and they disappear with them.
Checkpoint
Check: Reading a diagnostic
One compiler, several language modes
A language mode is the set of rules the compiler applies, and the Swift 6.4 compiler has four of them: Swift 6, 5, 4.2 and 4. The diagnostics documentation states it plainly: the compiler’s version and the language mode are different things. This program changes a global variable from a function, and its fixed version puts that function on the main actor:
// counter.swift: a global variable changed by a function.
var counter = 0
func increment() {
counter += 1
}
increment()
increment()
print("counter:", counter) // counter_fixed.swift: the same program, with increment() on the main actor.
var counter = 0
@MainActor
func increment() {
counter += 1
}
increment()
increment()
print("counter:", counter) from session import run, version
# swiftc without a language mode, then the Swift 6 mode (meant to fail), then the fix in the Swift 6 mode.
version()
run("""
swiftc counter.swift -o counter
./counter
!swiftc -language-mode 6 counter.swift -o counter
swiftc -language-mode 6 counter_fixed.swift -o counter_fixed
./counter_fixed
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc counter.swift -o counter
$ ./counter
counter: 2
$ swiftc -language-mode 6 counter.swift -o counter
counter.swift:5:5: error: main actor-isolated var 'counter' can not be mutated from a nonisolated context
1 | // counter.swift: a global variable changed by a function.
2 | var counter = 0
| `- note: mutation of this var is only permitted within the actor
3 |
4 | func increment() {
| `- note: add '@MainActor' to make global function 'increment()' part of global actor 'MainActor'
5 | counter += 1
| `- error: main actor-isolated var 'counter' can not be mutated from a nonisolated context
6 | }
7 |
[exit status 1]
$ swiftc -language-mode 6 counter_fixed.swift -o counter_fixed
$ ./counter_fixed
counter: 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 modes_session.py
A bare swiftc used the Swift 5 mode, and the program printed counter: 2. In the Swift 6 mode the same code is an
error: a top-level variable of the program belongs to the main actor, and a function that is not on the main actor may
not change it, because two threads could change it at once. The note proposes the fix that the third build applies,
@MainActor on increment(), and the program prints the same line as before. The Swift 6 mode did not change what the
code does; it refused code that could race, which the Swift 5 mode accepted.
Each kind of build chooses its mode in its own place:
swiftorswiftcon files:-language-mode 6, or its older spelling-swift-version 6. Without either, the mode is Swift 5.- A SwiftPM package: the tools version decides, and from
swift-tools-version: 6.0on the mode is Swift 6.swiftLanguageModes:sets the modes of the whole package, and.swiftLanguageMode(.v5)in a target’s settings keeps that one target in Swift 5. - An Xcode project: the Swift Language Version build setting,
SWIFT_VERSION.
In code, #if swift(>=6) asks for the language mode and #if compiler(>=6.4) for the compiler, which is how the doctor
program of the install lesson told them apart.
Key idea
One compiler applies one of several sets of rules: a flag, a package’s tools version or an Xcode setting picks the language mode, and the Swift 6 mode refuses code that could race instead of changing what the code does.
Checkpoint
Check: Language modes
Upcoming features bring the next mode’s rules early
Moving a large project to a new language mode in one step would mean fixing every new error at once. An upcoming feature: One rule of a future language mode that can be turned on early, on its own, with -enable-upcoming-feature or .enableUpcomingFeature in Package.swift. is one rule of a future mode that you can turn on now, alone. Walk through the phases:
- Swift 5 mode
The rules most existing code was written for
The default of a bare swift or swiftc, and of packages whose tools version is below 6.0. The checks that make data races compile errors are not part of it.
- Upcoming features
Swift 6 rules, one at a time
Most rules that the Swift 6 mode adds are also upcoming features, such as StrictConcurrency or GlobalConcurrency, and -enable-upcoming-feature turns one on in a Swift 5 target. The 6.4 compiler lists 15 features as enabled in 6.
- Swift 6 mode
All of them on, data-race safety by default this track
In the Swift 6 mode all 15 are on, and possible data races are compile errors. A build chooses it with -language-mode 6, a tools version of 6.0 or later, or the Swift Language Version setting of Xcode.
- Next mode
Six features planned for Swift 7
ExistentialAny, InternalImportsByDefault, MemberImportVisibility, InferIsolatedConformances, NonisolatedNonsendingByDefault and ImmutableWeakCaptures are planned for the next language mode, which the compiler calls 7. Each can be turned on today.
- Migration mode
Warnings that prepare the code
Four of the six offer a migration mode. With :migrate after the name, the feature stays off, and the compiler instead warns at each place that needs a change, with a fix-it that keeps the program's behaviour.
The timeline has 5 events, from Swift 5 mode to Migration mode.
The compiler can list the features it knows, with the mode that turns each one on. This file uses a protocol, a kind of type that a later module explains, in the way the next mode will reject:
// receipt.swift: a protocol used as a type, which the next language mode wants written as any Payment.
protocol Payment {
var amount: Int { get }
}
struct Cash: Payment {
let amount: Int
}
struct Card: Payment {
let amount: Int
}
func describe(_ payment: Payment) -> String {
"paid \(payment.amount) rupees"
}
let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
for payment in payments {
print(describe(payment))
} from session import run, version
# Which upcoming features the compiler knows, one turned on early, its migration mode, and a misspelled name.
version()
run("""
swiftc -print-supported-features | grep '"enabled_in"'
swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny:migrate -diagnostic-style llvm receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny -Wwarning StrictLanguageFeatures receipt.swift -o receipt
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -print-supported-features | grep '"enabled_in"'
{ "name": "ConciseMagicFile", "enabled_in": "6" },
{ "name": "ForwardTrailingClosures", "enabled_in": "6" },
{ "name": "StrictConcurrency", "enabled_in": "6" },
{ "name": "BareSlashRegexLiterals", "enabled_in": "6" },
{ "name": "DeprecateApplicationMain", "enabled_in": "6" },
{ "name": "ImportObjcForwardDeclarations", "enabled_in": "6" },
{ "name": "DisableOutwardActorInference", "enabled_in": "6" },
{ "name": "IsolatedDefaultValues", "enabled_in": "6" },
{ "name": "GlobalConcurrency", "enabled_in": "6" },
{ "name": "InferSendableFromCaptures", "enabled_in": "6" },
{ "name": "ImplicitOpenExistentials", "enabled_in": "6" },
{ "name": "RegionBasedIsolation", "enabled_in": "6" },
{ "name": "DynamicActorIsolation", "enabled_in": "6" },
{ "name": "NonfrozenEnumExhaustivity", "enabled_in": "6" },
{ "name": "GlobalActorIsolatedTypesUsability", "enabled_in": "6" },
{ "name": "ExistentialAny", "migratable": true, "categories": ["ExistentialAny"], "enabled_in": "7" },
{ "name": "InternalImportsByDefault", "enabled_in": "7" },
{ "name": "MemberImportVisibility", "migratable": true, "categories": ["MemberImportVisibility"], "enabled_in": "7" },
{ "name": "InferIsolatedConformances", "migratable": true, "categories": ["IsolatedConformances"], "enabled_in": "7" },
{ "name": "NonisolatedNonsendingByDefault", "migratable": true, "categories": ["NonisolatedNonsendingByDefault"], "enabled_in": "7" },
{ "name": "ImmutableWeakCaptures", "enabled_in": "7" }
$ swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny receipt.swift -o receipt
receipt.swift:14:26: warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
12 | }
13 |
14 | func describe(_ payment: Payment) -> String {
| `- warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
15 | "paid \(payment.amount) rupees"
16 | }
receipt.swift:18:16: warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
16 | }
17 |
18 | let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
| `- warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
19 | for payment in payments {
20 | print(describe(payment))
[#ExistentialAny]: <https://docs.swift.org/compiler/documentation/diagnostics/existential-any>
$ swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny:migrate -diagnostic-style llvm receipt.swift -o receipt
receipt.swift:14:26: warning: use of protocol 'Payment' as a type must be written 'any Payment' [#ExistentialAny]
func describe(_ payment: Payment) -> String {
^~~~~~~
any Payment
receipt.swift:18:16: warning: use of protocol 'Payment' as a type must be written 'any Payment' [#ExistentialAny]
let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
^~~~~~~
any Payment
$ swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny receipt.swift -o receipt
$ swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny -Wwarning StrictLanguageFeatures receipt.swift -o receipt
<unknown>:0: warning: 'ExistenialAny' is not a recognized upcoming feature [#StrictLanguageFeatures::UnrecognizedStrictLanguageFeatures]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 features_session.py
ExistentialAny turned two places into warnings that say they will be errors in a future language mode, so you can fix
them while the code still builds. ExistentialAny:migrate left the feature off and printed the same two places with
their fix-its, any Payment. The last two builds show a trap: a misspelled name, ExistenialAny, is ignored without a
word, because projects must build with several compiler versions; the StrictLanguageFeatures warning group reports it.
In a package, the same switch is a setting of a target, .enableUpcomingFeature("ExistentialAny"). The package of the
first program lesson already used one: ApproachableConcurrency, which, in the compiler’s source, stands for five
features at once, DisableOutwardActorInference, GlobalActorIsolatedTypesUsability, InferIsolatedConformances,
InferSendableFromCaptures and NonisolatedNonsendingByDefault. The concurrency modules of this track explain what
they change.
Checkpoint
Check: Upcoming features
Warnings can be raised, kept or silenced by group
A warning never stops a build, which is why teams that want a clean build promote warnings to errors. Swift 6.4 gives
that control per group, on the command line, in Package.swift and in the code. This file calls a deprecated function
in two places, one of them inside a function marked with @diagnose; the second file asks @diagnose to lower an
error:
// fares.swift: a deprecated function, called in two places.
@available(*, deprecated, renamed: "fare(km:)")
func oldFare(km: Int) -> Int { 20 + km * 8 }
func fare(km: Int) -> Int { 20 + km * 8 }
func monthlyPass() -> Int {
oldFare(km: 12) * 44
}
@diagnose(DeprecatedDeclaration, as: ignored, reason: "the old ticket machines need it")
func ticketMachineFare() -> Int {
oldFare(km: 12)
}
print(monthlyPass(), ticketMachineFare(), fare(km: 3)) // downgrade.swift: @diagnose asks for a warning, but this diagnostic is an error.
@diagnose(MutableGlobalVariable, as: warning)
struct Stall {
static var openStalls = 10
}
print(Stall.openStalls) from session import run, version
# One warning, four ways of treating it; then an error that no setting can lower. Three builds are meant to fail.
version()
run("""
swiftc -language-mode 6 fares.swift -o fares
./fares
!swiftc -language-mode 6 -Werror DeprecatedDeclaration fares.swift -o fares
!swiftc -language-mode 6 -warnings-as-errors fares.swift -o fares
swiftc -language-mode 6 -warnings-as-errors -Wwarning DeprecatedDeclaration fares.swift -o fares
!swiftc -language-mode 6 downgrade.swift -o downgrade
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 fares.swift -o fares
fares.swift:8:5: warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
6 |
7 | func monthlyPass() -> Int {
8 | oldFare(km: 12) * 44
| |- warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
| `- note: use 'fare(km:)' instead
9 | }
10 |
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
$ ./fares
5104 116 44
$ swiftc -language-mode 6 -Werror DeprecatedDeclaration fares.swift -o fares
fares.swift:8:5: error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
6 |
7 | func monthlyPass() -> Int {
8 | oldFare(km: 12) * 44
| |- error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
| `- note: use 'fare(km:)' instead
9 | }
10 |
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[exit status 1]
$ swiftc -language-mode 6 -warnings-as-errors fares.swift -o fares
fares.swift:8:5: error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
6 |
7 | func monthlyPass() -> Int {
8 | oldFare(km: 12) * 44
| |- error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
| `- note: use 'fare(km:)' instead
9 | }
10 |
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[exit status 1]
$ swiftc -language-mode 6 -warnings-as-errors -Wwarning DeprecatedDeclaration fares.swift -o fares
fares.swift:8:5: warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
6 |
7 | func monthlyPass() -> Int {
8 | oldFare(km: 12) * 44
| |- warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
| `- note: use 'fare(km:)' instead
9 | }
10 |
[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
$ swiftc -language-mode 6 downgrade.swift -o downgrade
downgrade.swift:4:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
2 | @diagnose(MutableGlobalVariable, as: warning)
3 | struct Stall {
4 | static var openStalls = 10
| |- error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
| |- note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
| |- note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
| `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
5 | }
6 |
[#MutableGlobalVariable]: <https://docs.swift.org/compiler/documentation/diagnostics/mutable-global-variable>
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 warnings_session.py
Only the call in monthlyPass is ever reported. -Werror DeprecatedDeclaration makes that group an error,
-warnings-as-errors makes every warning one, and -warnings-as-errors -Wwarning DeprecatedDeclaration keeps the
policy for everything except that group. Step through the decision the compiler makes for each call:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A deprecated call. Calling oldFare is a warning of the group DeprecatedDeclaration, as the first build of the session showed. It starts as a warning, and the next two stages may change that.
- -warnings-as-errors. The flags apply from left to right. -warnings-as-errors makes every warning an error, so on its own it stops the build at the call in monthlyPass.
- -Wwarning DeprecatedDeclaration. The next flag names the group and makes it a warning again. For each group the last flag wins, so with the flags in the other order the call would stay an error.
- @diagnose on a function. Inside ticketMachineFare, its @diagnose attribute comes last and says ignored, so that call is never reported, whatever the flags say. The call in monthlyPass has no attribute around it.
- The result. The call in monthlyPass is reported as a warning and the build goes on; the call in ticketMachineFare is not reported at all.
- Errors stay errors. Only warnings can be changed. An error, such as the mutable global of downgrade.swift in the Swift 6 mode, stays an error under every flag and every @diagnose.
Version note
-Werror and -Wwarning arrived in Swift 6.1, .treatWarning and .treatAllWarnings in Swift 6.2, and
@diagnose in Swift 6.4. An older compiler does not know the newer ones, so pin the toolchain before you rely on
them.
The same choices exist in each place a project keeps its settings, the command line, the target settings of
Package.swift and the code:
- Every warning an error:
-warnings-as-errors, or.treatAllWarnings(as: .error)on a target. The code has no switch for all groups at once. - One group an error:
-Werror DeprecatedDeclaration,.treatWarning("DeprecatedDeclaration", as: .error), or@diagnose(DeprecatedDeclaration, as: error)on a declaration. - One group a warning again:
-Wwarning DeprecatedDeclaration,.treatWarning("DeprecatedDeclaration", as: .warning), or@diagnose(DeprecatedDeclaration, as: warning). - One group not reported at all: only
@diagnose(DeprecatedDeclaration, as: ignored, reason: "…"), and only inside the declaration it marks; no flag hides a group for a whole module.
Silencing a check instead of fixing the code
nonisolated(unsafe) static var openStalls = 10 makes the error of stall.swift go away, but it only tells the
compiler to stop checking. Its documentation keeps it for state you protect yourself, with a lock for example. Make
the value a let, or put it on the main actor, and the compiler can keep checking for you.
Checkpoint
Check: Controlling warnings
Practise: three programs, three diagnostics
Each of these programs has one error, a different one each time. Read each diagnostic, decide the fix it points to, and compare with the fixed version at the end of the session:
func fare(km: Int) -> Int { 20 + km * 8 }
print(fare(12)) let seats = 40
seats -= 2
print(seats) let price = 25
let tax = 0.05
print(price * tax) // detectives_fixed.swift: the three programs, each fixed the way its diagnostic suggests.
func fare(km: Int) -> Int { 20 + km * 8 }
print(fare(km: 12))
var seats = 40
seats -= 2
print(seats)
let price = 25
let tax = 0.05
print(Double(price) * tax) from session import run, version
# Three programs with one error each (the builds are meant to fail), then the fixed version, which builds and runs.
version()
run("""
!swiftc -language-mode 6 detective_1.swift -o detective
!swiftc -language-mode 6 detective_2.swift -o detective
!swiftc -language-mode 6 detective_3.swift -o detective
swiftc -language-mode 6 detectives_fixed.swift -o fixed
./fixed
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 detective_1.swift -o detective
detective_1.swift:2:12: error: missing argument label 'km:' in call
1 | func fare(km: Int) -> Int { 20 + km * 8 }
2 | print(fare(12))
| `- error: missing argument label 'km:' in call
3 |
[exit status 1]
$ swiftc -language-mode 6 detective_2.swift -o detective
detective_2.swift:2:7: error: left side of mutating operator isn't mutable: 'seats' is a 'let' constant
1 | let seats = 40
| `- note: change 'let' to 'var' to make it mutable
2 | seats -= 2
| `- error: left side of mutating operator isn't mutable: 'seats' is a 'let' constant
3 | print(seats)
4 |
[exit status 1]
$ swiftc -language-mode 6 detective_3.swift -o detective
detective_3.swift:3:13: error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'
1 | let price = 25
2 | let tax = 0.05
3 | print(price * tax)
| |- error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'
| `- note: overloads for '*' exist with these partially matching parameter lists: (Double, Double), (Duration, Double), (Int, Int)
4 |
[exit status 1]
$ swiftc -language-mode 6 detectives_fixed.swift -o fixed
$ ./fixed
116
38
1.25
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 detective_session.py
The label km: was missing from the call, the note pointed at the let that had to become a var, and Int and
Double do not mix in arithmetic until one is converted. None of the fixes needed more than the diagnostic and its
notes. Then the questions below go through the whole lesson, weakest idea first:
Practice that adapts to you
Practise this lesson
10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what are errors, warnings and notes in Swift, and what does a name such as [#DeprecatedDeclaration] mean?
An error means the code cannot be compiled as written, so the build stops. A warning points at code that compiles but is
probably wrong or out of date, such as a deprecated call or a var that is never changed, and the build goes on. A note
adds detail to an error or a warning: another place involved, or a suggested fix. The name in brackets is the diagnostic
group, a stable identifier of that kind of diagnostic with its own page on docs.swift.org. Groups are what you control:
-Werror DeprecatedDeclaration turns that group’s warnings into errors, -Wwarning keeps them warnings, and
@diagnose changes them for one declaration. Errors themselves cannot be lowered, by flags or by @diagnose.
Key takeaways
- A diagnostic gives the location, the severity, the message and the group, followed by notes and fix-its; fix the first error first, and look up the group on docs.swift.org.
- One compiler supports several language modes:
swiftandswiftcdefault to Swift 5, a package with tools version 6.0 or later builds in Swift 6, and-language-modeor.swiftLanguageMode(.v5)chooses explicitly. - The Swift 6 mode turns possible data races into errors, such as a global variable changed from a function that is not on the main actor.
- Upcoming features turn on single rules of a future mode early, and their
:migrateform warns with fix-its instead; a misspelled feature name is ignored unlessStrictLanguageFeaturesis turned on. - Warnings are controlled by group with
-warnings-as-errors,-Werrorand-Wwarning, applied left to right, with.treatWarningand.treatAllWarningsin Package.swift and with@diagnosein code; errors cannot be lowered.
Terms in this lesson
- diagnostic
- A message from the compiler about the code: an error, which stops the build, a warning, which does not, or a note that adds detail. Each starts with the file, the line and the column.
- diagnostic group
- A stable name for a family of diagnostics, printed in brackets after the message, such as [#DeprecatedDeclaration]. Flags such as -Werror and the @diagnose attribute act on groups, and docs.swift.org explains each one.
- fix-it
- A ready-made edit attached to a diagnostic, such as replacing var with let. Editors offer to apply it, and -diagnostic-style llvm prints it under the code.
- language mode
- The set of language rules the compiler applies to your code: Swift 6, 5, 4.2 or 4. One compiler supports several modes, chosen with -language-mode or a package setting; the Swift 6 mode reports possible data races as errors.
- upcoming feature
- One rule of a future language mode that can be turned on early, on its own, with -enable-upcoming-feature or .enableUpcomingFeature in Package.swift.
References
- Swift compiler diagnostics (Swift.org)
- Diagnostic groups (Swift.org)
- Unsafe mutable global and static variables (MutableGlobalVariable) (Swift.org)
- Language mode and tools version (ErrorInFutureSwiftVersion) (Swift.org)
- Upcoming language features (Swift.org)
- Strict language feature enablement (StrictLanguageFeatures) (Swift.org)
- Migrating to Swift 6: Enable data-race safety checking (Swift.org)
- Migrating to Swift 6: Migrating to upcoming language features (Swift.org)
- The Swift Programming Language (6.4): Statements, Conditional Compilation Block (Swift.org)
- SE-0441: Formalize 'language mode' terminology (Swift Evolution (The Swift Project))
- SE-0435: Swift language version per target (Swift Evolution (The Swift Project))
- SE-0443: Precise control flags over compiler warnings (Swift Evolution (The Swift Project))
- SE-0480: Warning control settings for SwiftPM (Swift Evolution (The Swift Project))
- SE-0522: Source-level control over compiler warnings (Swift Evolution (The Swift Project))
- SE-0486: Migration tooling for Swift features (Swift Evolution (The Swift Project))
- PackageDescription: SwiftSetting.enableUpcomingFeature(_:_:) (Apple)
- PackageDescription: SwiftSetting.treatWarning(_:as:_:) (Apple)
- Swift compiler source: CompilerInvocation.cpp (ApproachableConcurrency) (The Swift Project (swiftlang))
Report a problem with this lesson
Kept only in this browser. Your Learn progress