Skip to content

Prefer FoundationEssentials when it's available - #334

Open
nmcc24 wants to merge 1 commit into
dduan:mainfrom
nmcc24:prefer-foundation-essentials
Open

nmcc24 wants to merge 1 commit into
dduan:mainfrom
nmcc24:prefer-foundation-essentials

Conversation

@nmcc24

@nmcc24 nmcc24 commented Nov 8, 2026 •

Copy link
Copy Markdown

Summary

  • Import FoundationEssentials instead of Foundation where the toolchain provides it, and fall back to Foundation elsewhere. public import Foundation in TOMLDecoder.swift and DateTime.swift stays public in both branches.
  • Guard the Date and DateComponents support with canImport(FoundationEssentials) || canImport(Foundation), so it's available with either module.
  • Update TOMLSingleValueDecodingContainer.swift.gyb along with its generated file.

Impact

On Apple platforms nothing changes: FoundationEssentials isn't importable there, so the #else branch imports Foundation as before.

On Linux, Windows, Android and WASI, Foundation is swift-corelibs-foundation. It re-exports FoundationEssentials, and also brings in FoundationInternationalization, CoreFoundation and ICU (_FoundationICU). Because TOMLDecoder uses public import Foundation, every package that depends on it gets full Foundation imported and linked, even when that package only needs FoundationEssentials.

With this change, such a package can:

  • when linked dynamically, ship without libFoundation.so, libFoundationInternationalization.so and lib_FoundationICU.so. For a minimal program on Linux with Swift 6.3, the Foundation libraries it loads drop from about 60 MB to about 9 MB, and its peak memory from about 16 MB to about 10 MB;
  • when linked statically (--static-swift-stdlib, the static Linux SDK), keep full Foundation off the link line, so CoreFoundation and ICU's data can't be pulled in;
  • on WASI, build against the lighter module;
  • compile against a smaller module.

TOMLDecoder only needs Data, Date, Calendar, TimeZone, DateComponents and Decimal, which are all in FoundationEssentials. swift-argument-parser already imports FoundationEssentials the same way.

Validation

  • swift test (751 tests) on Linux (Swift 6.3) and macOS (Xcode 27.2)
  • Scripts/test-generate.sh
  • Scripts/format.sh --lint .

Import FoundationEssentials instead of Foundation where the toolchain
provides it, and fall back to Foundation elsewhere. The Date and
DateComponents support is guarded by either module being available.
@nmcc24

nmcc24 commented Nov 8, 2026

Copy link
Copy Markdown
Author

@dduan please do let me know your thoughts on this. Thanks

@dduan

dduan commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

hey @nmcc24 thanks! I love the idea. But looks like the following would cash on Linux:

import FoundationEssentials
import TOMLDecoder

struct Value: Decodable {
    let datetime: Date
}

var decoder = TOMLDecoder()
decoder.strategy.date = .calendar(identifiedBy: .islamic)

let value = try decoder.decode(
    Value.self,
    from: "datetime = 2021-01-01T00:00:00Z"
)
print(value.datetime.timeIntervalSince1970)

@nmcc24

nmcc24 commented Nov 11, 2026

Copy link
Copy Markdown
Author

hey @nmcc24 thanks! I love the idea. But looks like the following would cash on Linux:

import FoundationEssentials
import TOMLDecoder

struct Value: Decodable {
let datetime: Date
}

var decoder = TOMLDecoder()
decoder.strategy.date = .calendar(identifiedBy: .islamic)

let value = try decoder.decode(
Value.self,
from: "datetime = 2021-01-01T00:00:00Z"
)
print(value.datetime.timeIntervalSince1970)

@dduan Thanks for catching this, and sorry for missing it. FoundationEssentials only provides the Gregorian and ISO 8601 calendars. For any other identifier, Calendar(identifier:) force-unwraps the ICU class from FoundationInternationalization, so it traps unless something else links full Foundation. Our tests only use .gregorian and the test runner links Foundation, which is why they passed.

Making Essentials the default would change behaviour for existing users, so I'd suggest an opt-in instead: a default-enabled package trait that keeps import Foundation. Consumers who disable it get FoundationEssentials, and .calendar(identifiedBy:) throws a DecodingError for unsupported calendars there instead of crashing. That needs tools-version 6.1. If that's more than you want to carry, I'm happy to close this.

@dduan

dduan commented Nov 13, 2026

Copy link
Copy Markdown
Owner

A breaking change at runtime for existing users would not be possible, unfortunately.

@nmcc24

nmcc24 commented Nov 13, 2026

Copy link
Copy Markdown
Author

A breaking change at runtime for existing users would not be possible, unfortunately.

@dduan Agreed, and to be clear, I'm not proposing that anymore. The current diff still makes FoundationEssentials the default, which is exactly the runtime break you found. I'll rework it so existing users see no change at all:

  • A new package trait, enabled by default, keeps import Foundation. Anyone who doesn't touch traits builds and behaves exactly as today.
  • Only consumers who explicitly disable it get FoundationEssentials. In that mode, .calendar(identifiedBy:) with a calendar Essentials doesn't support throws a DecodingError instead of trapping.
  • The trait lives in a Package@swift-6.1.swift manifest, so Swift 6.0 users keep building from the current Package.swift.

Would you be open to that? If so, I'll update the PR. If not, no problem, I'll close it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants