Skip to content

Foundation bridge coverage: five generator limits block most everyday scripting #7

Description

@odrobnik

Bridge coverage is broad but thin: 2,593 generated entries, yet most everyday scripting tasks still hit a wall. Almost all of it traces to a handful of generator limitations rather than missing allowlist entries, so this is mostly one structural fix away from a much larger surface.

Measured against main (71605b2) by building swift-script and running probe scripts, plus diffing Apple's Foundation symbol graph against the emitted bridges.

What a script cannot do today

Each of these is a task someone would plausibly write a script for, and each currently fails:

Task Fails with
Extract text with a regex cannot find 'NSRegularExpression' in scope; regex literals hit unsupported expression RegexLiteralExprSyntax; String.range(of:) missing
Copy/move a file, read its attributes 'FileManager' has no method 'copyItem' (also attributesOfItem, createFile, enumerator, temporaryDirectory, every URL overload)
Parse or format a date cannot find 'DateFormatter' in scope, cannot find 'ISO8601DateFormatter' in scope; Date.formatted() is generated zero-arity only
Read untyped JSON type 'JSONSerialization' has no member 'jsonObject' — no bridge file at all
POST, or send a header cannot find 'URLRequest' in scope, cannot find 'URLQueryItem' in scope — HTTP is GET-only
Check an HTTP status properly response as? HTTPURLResponse fails: could not cast value of type 'URLResponse' to 'HTTPURLResponse'
Shell out Process constructs, but executableURL / launchPath / arguments are all has no settable member; Pipe unbridged
Sleep / poll / retry Task.sleep unsupported, Thread unbridged — no way to wait
Append to Data no append, subdata(in:), subscript or range(of:) — 11 of 112 members bridged
Write to stderr FileHandle has no write; cannot find 'fputs' in scope
Set an encoder strategy has no settable member 'dateEncodingStrategy' (JSONEncoder 2/14 members, JSONDecoder 3/14)

Root causes, in leverage order

These are generator-level, so each one unlocks a category rather than a symbol:

  1. Collection-shaped signatures are rejected outright. extractType refuses any signature containing [, so no [String], [String: String] or Set<T> parameter or return ever bridges. This single rule removes a large fraction of Foundation. IdentityModule hand-rolls exactly these shapes, which shows the runtime already supports them — it is the generator that declines. Highest-leverage change available.
  2. Optional parameters are skipped (if t.isOptional { return nil }) while optional returns already work. A lot of Foundation takes String? / URL?. The asymmetry reads as unfinished rather than deliberate.
  3. One bridge key per (receiver, name), labels excluded, so labelled overloads are structurally unreachable — which is why ~14 URLSession entries sit in the blocklist. Needs a signature-keyed dispatcher; blocklist edits cannot fix it.
  4. mutating methods and struct property setters cannot bridge at all — writeback through an .opaque value is unmodelled, so the receiver arrives by value. This is why Data.append and every Process setter are missing.
  5. Only Int and Double cross the boundary. No Int32, UInt8, FloatProcessInfo.processIdentifier: Int32 had to be hand-rolled.

Two concrete bugs found while measuring

FileManager.default returns a sentinel, so 19 of its 23 generated bridges are dead code. Interpreter+StaticMembers.swift:89 returns fileManagerSentinel, and dispatch runs through a hardcoded switch in Interpreter+FileIO.swift supporting only fileExists, contentsOfDirectory, removeItem and createDirectory (String paths only) before throw RuntimeError.invalid("'FileManager' has no method '\(name)'"). The generated bridges exist and are unreachable. temporaryDirectory fails with the especially confusing expected FileManager, got FileManager.

Resources/foundation-allowlist.txt is dead. Tools/regen-foundation-bridge.sh passes --auto-allowlist and never --allowlist; the file is referenced nowhere else in the repo. All 80 of its entries are already covered by auto-harvest, so it is both unreferenced and subsumed — worth deleting so it stops implying it controls anything.

Also, Tools/bridge-metrics.sh greps StdlibBridge.generated.swift / FoundationBridge.generated.swift, which the generator now deletes — the dashboard silently reports total-bridges: 0.

Where the headline number is misleading

Of 2,593 Foundation entries, 1,405 (54%) are .staticValue constants, and 17 constant-bag types account for 974 of them (LocaleRegion alone is 263). Meanwhile 80 of 203 bridged types have ≤5 entries. Usable API surface is much smaller than the count suggests — worth tracking "types with a working method" as a separate metric.

Deliberately excluded — not proposed here

For completeness, so these are not re-litigated: the macOS 13 / iOS 16 availability floor (inherited from SwiftBash, excludes the post-2022 FormatStyle families), sandbox gating of IO through Shell.current, the 15-name hand-verified class allowlist (each addition risks NSObject method-name shadowing), and ObjC-runtime-dependent APIs.

Reproducing

swift build --scratch-path /tmp/ss
echo 'import Foundation
print(FileManager.default.temporaryDirectory)' > /tmp/p.swift
/tmp/ss/debug/swift-script /tmp/p.swift

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions