1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Test module: diagnostic output to stdout/stderr is expected here. ~keep
#![allow(clippy::print_stdout, clippy::print_stderr)]
//! Regression test: a fieldless enum passed as a function/method parameter must produce a
//! swift crate that actually *compiles*.
//!
//! Before this fix, the swift backend emitted a call like
//! `client.0.analyze(&input, <toolkit::Mode as ::std::convert::From<String>>::from(mode))` for
//! `fn analyze(&self, input: String, mode: Mode) -> Result<String, String>`. Two independent
//! defects hid on that one line:
//!
//! 1. `<toolkit::Mode as ::std::convert::From<String>>::from(...)` assumes a `From<String>`
//! impl on the consumer's own enum that alef never emits (and, being both a foreign trait
//! and a foreign type from the swift crate's point of view, *cannot* legally emit -- it
//! would be an orphan impl, E0117). It fails with E0277: `Mode: From<String>` is not
//! satisfied.
//! 2. `&input` borrows a `String` parameter the core method takes by value. It fails with
//! E0308: expected `String`, found `&String`.
//!
//! Both were invisible to text-assertion tests: the emitted line reads as plausible Rust. Only
//! `rustc` rejects it. This test therefore does not assert on emitted text -- it drives the real
//! `alef` binary over a fixture crate and runs `cargo build` over the emitted swift crate, the
//! same tool a consumer's build uses.
//!
//! # Why this fixture is not the multi-language gate fixture
//!
//! [`generated_output_downstream_gate`]'s fixture emits every language into one project and
//! relies on `[workspace.output_template]` defaults for `crates/{name}-ffi`. Swift's generated
//! `Cargo.toml` unconditionally depends on that FFI crate by a fixed relative path
//! (`../../../crates/{name}-ffi`, see `cargo_toml_depends_on_ffi_crate` in
//! `src/backends/swift/gen_rust_crate/cargo.rs`) purely so the FFI crate's `#[no_mangle]`
//! exports get linked into the final staticlib -- nothing in the swift-generated Rust actually
//! calls into it beyond one keep-linked probe function. This test hand-places a minimal stub
//! at that exact path instead of asking `ffi` to also generate: it is the smallest fixture that
//! reproduces the real dependency edge without dragging in the (separate, pre-existing)
//! question of what alef's default `output_template` should resolve the ffi crate to.
//!
//! Ignored like the other downstream-toolchain lanes ([`generated_output_downstream_gate`]):
//! it compiles a real crate against the Swift toolchain, too slow for the default
//! `cargo test --workspace` matrix. Run explicitly via
//! `cargo test --test swift_fieldless_enum_param_compiles -- --ignored`.
use std::path::Path;
use std::process::Command;
const FIXTURE_CORE_CARGO_TOML: &str = r#"[package]
name = "toolkit"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", features = ["derive"] }
"#;
const FIXTURE_CORE_SOURCE: &str = r#"
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum Mode {
Fast,
Thorough,
}
pub struct Session {
token: String,
}
impl Session {
pub fn new(token: String) -> Self {
Self { token }
}
pub fn analyze(&self, input: String, mode: Mode) -> Result<String, String> {
let _ = (input, mode);
Err("unimplemented".to_string())
}
}
"#;
/// A trivial stand-in for the real `alef`-generated FFI crate. The swift crate's `Cargo.toml`
/// depends on `toolkit-ffi` purely to keep its `#[no_mangle]` exports linked into the final
/// staticlib (see the module doc), and the swift-generated `lib.rs` references exactly one
/// symbol from it -- a "keep linked" probe named `{crate}_version`. Nothing else about the real
/// FFI crate's content matters to this test, which is scoped to the swift crate's own generated
/// code.
const FIXTURE_FFI_CARGO_TOML: &str = r#"[package]
name = "toolkit-ffi"
version = "0.1.0"
edition = "2024"
"#;
const FIXTURE_FFI_SOURCE: &str = r#"
/// # Safety
/// Stub used only to satisfy the swift crate's keep-linked probe.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn toolkit_version() -> *const std::ffi::c_char {
static VERSION: &[u8] = b"0.1.0\0";
VERSION.as_ptr().cast::<std::ffi::c_char>()
}
"#;
const FIXTURE_ALEF_TOML: &str = r#"
[workspace]
alef_version = "__ALEF_VERSION__"
languages = ["swift"]
[[crates]]
name = "toolkit"
sources = ["src/lib.rs"]
version_from = "Cargo.toml"
[crates.generate]
public_api = true
"#;
fn run(program: &str, args: &[&str], cwd: &Path) -> (bool, String) {
let output = Command::new(program)
.args(args)
.current_dir(cwd)
.output()
.unwrap_or_else(|error| panic!("running `{program} {}` in {}: {error}", args.join(" "), cwd.display()));
let mut combined = String::from_utf8_lossy(&output.stdout).into_owned();
combined.push_str(&String::from_utf8_lossy(&output.stderr));
(output.status.success(), combined)
}
#[test]
#[ignore = "compiles a real swift crate against the Swift toolchain; run via \
`cargo test --test swift_fieldless_enum_param_compiles -- --ignored`"]
fn emitted_swift_crate_compiles_with_a_fieldless_enum_parameter() {
let workspace = tempfile::tempdir().expect("create fixture workspace");
let root = workspace
.path()
.canonicalize()
.unwrap_or_else(|_| workspace.path().to_path_buf());
std::fs::create_dir_all(root.join("src")).expect("create fixture src directory");
std::fs::write(root.join("src/lib.rs"), FIXTURE_CORE_SOURCE).expect("write fixture core source");
std::fs::write(root.join("Cargo.toml"), FIXTURE_CORE_CARGO_TOML).expect("write fixture core Cargo.toml");
std::fs::create_dir_all(root.join("crates/toolkit-ffi/src")).expect("create fixture ffi src directory");
std::fs::write(root.join("crates/toolkit-ffi/src/lib.rs"), FIXTURE_FFI_SOURCE)
.expect("write fixture ffi stub source");
std::fs::write(root.join("crates/toolkit-ffi/Cargo.toml"), FIXTURE_FFI_CARGO_TOML)
.expect("write fixture ffi stub Cargo.toml");
let config = FIXTURE_ALEF_TOML.replace("__ALEF_VERSION__", env!("CARGO_PKG_VERSION"));
std::fs::write(root.join("alef.toml"), config).expect("write fixture alef.toml");
// `alef generate`'s swift post-build step itself runs `cargo build` over the crate it just
// emitted (see the doc-comment on this test): a failing enum-parameter conversion surfaces
// right here, as a failure of `alef generate` -- there is no separate "now compile it"
// step to run afterward.
let (passed, output) = run(env!("CARGO_BIN_EXE_alef"), &["generate"], &root);
assert!(
passed,
"`alef generate` failed over the fieldless-enum-param fixture -- the emitted swift \
crate did not compile:\n{output}"
);
}