Skip to content

Build and check Swift snippets#42

Open
nickpdemarco wants to merge 10 commits intomainfrom
npd/swift-check
Open

Build and check Swift snippets#42
nickpdemarco wants to merge 10 commits intomainfrom
npd/swift-check

Conversation

@nickpdemarco
Copy link
Member

This PR adds support for checking Swift examples, copying the Rust-mdbook experience as much as possible: lines beginning with # will be omitted from the book output, but are used to compile the examples. Additionally, as a preprocessor step, all { ... } blocks are replaced with { fatalError() }.

# let a = [1, 2, 3]
# let x = 1
var i = 0
while (i != a.count && a[i] != x) {
  i += 1
}

A block marked with swift,ignore will not be built.

The examples can be checked with mdbook build. Several examples were slightly changed to pass the check. Without the two # lines above, the following error is emitted.

$ mdbook build better-code 
2026-02-03 16:23:39 [INFO] (mdbook::book): Book building has started
2026-02-03 16:23:39 [INFO] (mdbook::book): Running the html backend
2026-02-03 16:23:40 [INFO] (mdbook::book): Running the swift-test backend
2026-02-03 16:23:40 [INFO] (mdbook::renderer): Invoking the "swift-test" renderer
============================================================
Swift Code Example Testing
============================================================

Results: 19 passed, 1 failed, 20 total

------------------------------------------------------------
FAILURES:
------------------------------------------------------------

FAIL: chapter-2-contracts.md:238
/var/folders/zm/ll4504dd6dgbvy20y9ls2_hr0000gq/T/tmpwu0wf27f.swift:2:13: error: cannot find 'a' in scope
1 | var i = 0
2 | while (i != a.count && a[i] != x) {
  |             `- error: cannot find 'a' in scope
3 |   i += 1
4 | }

/var/folders/zm/ll4504dd6dgbvy20y9ls2_hr0000gq/T/tmpwu0wf27f.swift:2:24: error: cannot find 'a' in scope
1 | var i = 0
2 | while (i != a.count && a[i] != x) {
  |                        `- error: cannot find 'a' in scope
3 |   i += 1
4 | }

/var/folders/zm/ll4504dd6dgbvy20y9ls2_hr0000gq/T/tmpwu0wf27f.swift:2:32: error: cannot find 'x' in scope
1 | var i = 0
2 | while (i != a.count && a[i] != x) {
  |                                `- error: cannot find 'x' in scope
3 |   i += 1
4 | }

============================================================
2026-02-03 16:23:44 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
2026-02-03 16:23:44 [ERROR] (mdbook::utils): Error: Rendering failed
2026-02-03 16:23:44 [ERROR] (mdbook::utils):    Caused By: The "swift-test" renderer failed

@dabrahams
Copy link
Collaborator

SwiftyLab/setup-swift is the reliable Swift installation action we've been using for Hylo, FWIW.

# Preprocessor to hide `# ` lines in Swift code blocks (like mdbook does for Rust)
# Only strips lines for HTML output; swift-test backend sees the full code
[preprocessor.swift-hidden]
command = "python3 scripts/mdbook-swift-hidden.py"
Copy link
Member

Choose a reason for hiding this comment

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

Can you use the built-in mdbook support?

[output.html.code.hidelines]
swift = "#"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Swift has # directives so might need a different character

Copy link
Member Author

Choose a reason for hiding this comment

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

It seems to work! Huge simplification, thanks for pointing that out.

@dabrahams
Copy link
Collaborator

I got partway through reading the actual code which began to smell sloppy, and when I reached the point that it decided there was a case it wasn't going to support, and it would just exit with success (the output is not HTML), that was an almost certain sign that this was generated with AI and not carefully reviewed. Please forgive me if I've gotten that wrong, but Everything we check in here, even tooling should uphold the principles of better code. that includes real meaningful contract documentation for example.

@nickpdemarco
Copy link
Member Author

nickpdemarco commented Feb 4, 2026

Thanks for that feedback @dabrahams - I'll take a closer look and address what you've mentioned. In hindsight I should have made this a draft PR; it wasn't my intention to request a review, just share progress.

/// manager of any other employee.
public remove(_ e: EmployeeID)

...
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not attached to the ellipsis, but do we /have/ to drop it? Something like this tool ought to be able to skip them.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we don't have to drop it. I could add support for simply removing those. Would it be sufficient to remove all lines that contain only whitespace and ... exactly?

collection type:

```swift
# struct DynamicArray<Element> {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not PR-related: is it a problem that we're using DynamicArray here and MyArray elsewhere?


```swift
extension Collection {
extension Collection where Element: Hashable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this AI slop?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. Returning Set<Element> requires the Element to be Hashable.

FAIL: chapter-2-contracts.md:1382
/var/folders/zm/ll4504dd6dgbvy20y9ls2_hr0000gq/T/tmpoeblzo53.swift:2:54: error: type 'Self.Element' does not conform to protocol 'Hashable'
1 | extension Collection {
2 |   func bucketed(per bucket: (Element)->Int) -> [Int: Set<Element>] { [:] }
  |                                                      `- error: type 'Self.Element' does not conform to protocol 'Hashable'
3 | }
4 | (0..<10).bucketed { $0 % 2 }

============================================================



@dataclass(frozen=True)
class TestResult:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this should exist. You should instead propagate exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I disagree. Propagating exceptions would make it difficult to collect the test failures and present all of them at once. A propagated-exception model would encourage the caller to bail on the first test failure, but it's useful to users to see all of their failures in one place (so they don't have to needlessly rebuild). To support something like that with an exception propagation model, we'd need a cumbersome try/catch/append at the call site.

"""Yields Swift code blocks from markdown content."""
for m in FENCE.finditer(content):
lang, ignore = parse_attributes(m.group(1))
if lang == "swift":
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we're only handling swift why is FENCE matching everything and why is parse_attributes returning a language.

Copy link
Member Author

Choose a reason for hiding this comment

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

First, creating a regex to match both swift,ignore and ignore,swift (which seemed useful to support) was needlessly complex. Second, it's entirely possible we'll want to add support for other languages (e.g. Dafny, already referenced in this chapter), so the added regex complexity didn't seem worth it.

Copy link
Collaborator

@dabrahams dabrahams left a comment

Choose a reason for hiding this comment

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

I feel like I'm still reviewing AI-generated nonsense here. I'm not going further until you tell me I'm wrong, in which case we should probably have teams call to look at the code together and talk it over.

@nickpdemarco
Copy link
Member Author

Thanks for your feedback @dabrahams. I've resolved or addressed the issues you've expressed so far.

// Returns the `i`th element.
@requires(i >= 0 && i < self.count)
fun getNth(i: Integer): T
fun getNth(i: Integer) -> T
Copy link
Member Author

Choose a reason for hiding this comment

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

Wait, @dabrahams is this swift or hylo? I'm seeing fun and : T which makes me unsure.

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.

3 participants