🚥

Swift 5.8 で存在型(existential types)を用いているところに「any」を付けていないときに、コンパイルエラーにする

2023/02/21に公開

「Swift 5.8 の -enable-upcoming-feature フラグを用いて、存在型(existential types)を用いているところに any キーワードが付けられていない場合はコンパイルエラーにする」というタイトルにしたかったのですが、文字数制限に引っかかりました😇

本記事の内容は potatotips #81 iOS/Android開発Tips共有会(2023年2月21日 開催)の登壇資料(原稿)です。
https://potatotips.connpass.com/event/272353/

Summary

  • Swift 5.8 から「将来追加予定の言語改善への段階的な適用[1]」を行うためのコンパイラフラグ(-enable-upcoming-feature)などが追加される(SE-0362
  • Swift Package(Package.swift)のターゲットで、存在型(existential types)を用いているところに any キーワードが付けられていない場合にコンパイルエラーとするには、「SwiftSetting.enableUpcomingFeature(_:_:)」で name"ExistentialAny" を指定する
  • Xcode プロジェクトのターゲットで、存在型(existential types)を用いているところに any キーワードが付けられていない場合にコンパイルエラーとするには、「Swift Compiler - Custom Flags」>「Other Swift Flags(OTHER_SWIFT_FLAGS)」に -enable-upcoming-feature ExistentialAny を追加する

存在型(existential types)を用いるところでは any を付ける

Swift 5.6 から、存在型(existential types)を示す際に any キーワードを付けられるようになりました。ただ、Swift 5.6・5.7・5.8 では any キーワードを付けていなくてもコンパイル時に警告やエラーとはなりません。しかし Swift 6 では any キーワードを付けることが必須となります。

protocol Fruit { /* ... */ }

var fruit1: Fruit     // ✅
var fruit2: any Fruit // ✅

Swift 5.8 では「将来追加予定の言語改善への段階的な適用」ができる

any キーワードを付けることになる存在型(existential types)と、Opaque 型(opaque types)を取り扱う際に付けることがある some キーワードはほぼ対の関係になっているため、コンパイルエラー等にならない「any キーワードの付け忘れ」を Swift 6 がやってくる前の今のうちから無くしたいという気持ちがあります(Swift 6 では他にも破壊的変更が予定されているため、溜め続けてしまうと移行が大変になる未来が見えます)。

この「Swift 6 よりも前のうちから、この "any キーワードの付け忘れ" のときはコンパイルエラーにしてほしい!」などの用途に使える、新しい機能が Swift 5.8 で追加されます。それが「将来追加予定の言語改善への段階的な適用[1:1]」です。

「将来追加予定の言語改善への段階的な適用」を行うには、Swift コンパイラに追加された -enable-upcoming-feature X というようなコンパイラフラグを用います。「X」には、適用させたい "将来追加予定の言語改善" の機能の名前を指定します。たとえば、今回の「存在型(existential types)を用いるところでは any を付ける」ときは「-enable-upcoming-feature ExistentialAny」とします。

-enable-upcoming-feature ExistentialAny を付ける

それでは、この -enable-upcoming-feature ExistentialAny を付けてみましょう。本記事では「Swift Package のターゲットに付ける」・「Xcode プロジェクトのターゲットに付ける」の2つの使用例を紹介します。

Swift Package のターゲットで ExistentialAny の適用を行う

Swift Package のターゲットに対して「将来追加予定の言語改善への段階的な適用」を行うには、ターゲットの swiftSettings で「SwiftSetting.enableUpcomingFeature(_:_:)」を用います(swift-tools-version は 5.8 以降である必要があります)。

// swift-tools-version: 5.8

import PackageDescription

let package = Package(
    name: "MyPackage",
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyTarget"]),
    ],
    targets: [
        .target(
            name: "MyTarget",
            swiftSettings: [
              .enableUpcomingFeature("ExistentialAny", .when(configuration: .debug)),
            ]
        ),
    ]
)

SwiftSetting.enableUpcomingFeature(_:_:) の第1引数の name には文字列で "将来追加予定の言語改善" の機能の名前を指定します。今回は "ExistentialAny" を渡しています。第2引数の condition には BuildSettingCondition を渡すことができます。

import Foundation

protocol Fruit { /* ... */ }

public struct MyTarget {
    var fruit: Fruit!
    /// "ExistentialAny" が有効のときは、
    /// Use of protocol 'Fruit' as a type must be written 'any Fruit'
    /// というコンパイルエラーになる。
    /// `var fruit: (any Fruit)!` でコンパイルに成功する。
}

Xcode プロジェクトのターゲットで ExistentialAny の適用を行う

Xcode プロジェクトのターゲットに対して「将来追加予定の言語改善への段階的な適用」を行うには、ターゲットから Swift コンパイラの設定を行います。


-enable-upcoming-feature を使うには Swift 5.8(Xcode 14.3)以降が必要ですが、本記事公開時点で Xcode 14.3 は beta 版のため、スクリーンショットは Xcode 14.2 のものを用いています(Swift 5.8(Xcode 14.3)より前のままでは「Driver threw unknown argument: '-enable-upcoming-feature' without emitting errors.」というエラーとなり、ビルドできません)。

「将来追加予定の言語改善への段階的な適用」を行いたいターゲットの「Build Settings」にある、「Swift Compiler - Custom Flags」>「Other Swift Flags(OTHER_SWIFT_FLAGS)」に -enable-upcoming-feature ExistentialAny を追加します。

import SwiftUI

protocol Fruit { /* ... */ }

@main
struct MyApp: App {
    var fruit: Fruit!
    /// "ExistentialAny" が有効のときは、
    /// Use of protocol 'Fruit' as a type must be written 'any Fruit'
    /// というコンパイルエラーになる。
    /// `var fruit: (any Fruit)!` でコンパイルに成功する。
    
    var body: some Scene { /* ... */ }
}

(おまけ)すべての問題(issues)を一気に修正する

今回の「ExistentialAny の適用を行う」を行った場合、プロジェクトによっては「Use of protocol '*' as a type must be written 'any *'」というエラーが多数表示されるかもしれません。

「Use of protocol '*' as a type must be written 'any *'」の場合は「Replace '*' with 'any *'」と修正提案を出してくれるため、Xcode の「Fix」ボタンを押していくとそれが適用されていきます。この数が多い場合はメニューの「Editor」>「Fix all issues」で一気にその提案を適用できます。


スクリーンショットは Xcode 14.2 のものです。

(しかし、「ExistentialAny」において Fruit?Fruit!(any Fruit)?(any Fruit)! へと変更する必要がありますが、コンパイラが提案してくる修正が any Fruit?any Fruit! のため、人間の手で直していく必要があります……。)

参考

Xcode 14.3 Beta Release Notes | Apple Developer Documentation
https://developer.apple.com/documentation/xcode-release-notes/xcode-14_3-release-notes

swift-evolution/0362-piecemeal-future-features.md at main · apple/swift-evolution

SwiftPodcast/Swift 将来追加予定の言語改善への段階的な適用.md at main · stzn/SwiftPodcast

脚注
  1. 原文は "Piecemeal adoption of upcoming language improvements"。日本語訳は stzn/SwiftPodcast をもとにした。 ↩︎ ↩︎

Discussion