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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
use std::path::PathBuf;
use rustyfi_syntax::{ParseFileError, RustyfiVersion};
/// Everything that can go wrong while loading a multi-file SATySFi program.
#[derive(Debug, thiserror::Error)]
pub enum LoadError {
/// Could not read a source file from disk.
#[error("{path}: {source}")]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// A file failed to parse (lex or grammar error). `source` carries the
/// original [`ParseFileError`] (span + message) unchanged.
#[error("{path}: {source}")]
Parse {
path: PathBuf,
#[source]
source: ParseFileError,
},
/// `@require: name` could not be resolved to any file on disk.
#[error(
"cannot resolve `@require: {name}`; searched: {}",
format_searched(.searched)
)]
UnresolvedRequire {
name: String,
searched: Vec<PathBuf>,
},
/// `@import: name` could not be resolved to any file on disk.
#[error(
"cannot resolve `@import: {name}` from {}; searched: {}",
.from.display(),
format_searched(.searched)
)]
UnresolvedImport {
name: String,
from: PathBuf,
searched: Vec<PathBuf>,
},
/// The dependency graph contains a cycle; `chain` names the files
/// involved, in traversal order, with the first file repeated at the end
/// to make the loop explicit (e.g. `[a, b, a]`).
#[error("dependency cycle detected: {}", format_chain(.chain))]
Cycle { chain: Vec<PathBuf> },
/// A file reached via `@require:`/`@import:` is a document (has a body)
/// rather than a library.
#[error(
"{path}: required/imported file must be a library (no `in ...` body), found a document"
)]
DocumentAsDependency { path: PathBuf },
/// The entry file is a library (no body) rather than a document.
#[error("{path}: entry file must be a document (with an `in ...` body), found a library")]
LibraryAsEntry { path: PathBuf },
/// `opts.version` names a SATySFi version this port does not implement
/// yet (checked before any file is even read).
#[error(
"SATySFi {requested} documents are not supported yet; supported: {}",
format_versions(.supported)
)]
UnsupportedVersion {
requested: RustyfiVersion,
supported: Vec<RustyfiVersion>,
},
/// `LoadMode::Envelopes` was requested for a version with no `use`
/// headers — the rejected combination. Checked before any file is read.
#[error(
"SATySFi {version} has no `use` headers; `Envelopes` mode requires 0.1 \
(drop --deps, or pass --lang 0.1)"
)]
InvalidModeVersion { version: RustyfiVersion },
/// A [`crate::SourceProvider`] was supplied alongside `LoadMode::Envelopes`.
/// The Envelopes backend reads its deps/envelope configs through `std::fs`
/// directly, so honouring the provider for some reads and not others would
/// assemble a dependency graph half out of memory and half off the disk.
#[error(
"a custom source provider is only honoured in Legacy mode; \
`Envelopes` reads its deps/envelope configs from the filesystem directly"
)]
SourceProviderUnderEnvelopes,
/// `use package Mod` with no deps config to resolve `Mod` against
/// (upstream's used_as map is empty).
#[error(
"{from}: cannot resolve `use package {module}` — no pre-resolved \
dependency graph; pass --deps <rustyfi-deps.yaml>"
)]
PackageDependencyUnresolved { module: String, from: PathBuf },
/// Bare `use Mod` at document/open level (upstream `CannotUseHeaderUse`):
/// a document cannot reach into a package's internals by module name.
/// Permanent (not a stub): the closed resolver handles bare `use` only
/// *inside* envelope source trees.
#[error(
"{from}: bare `use {module}` is only allowed between files inside one \
package; a document must say `use package {module}` or `use {module} \
of \"<path>\"`"
)]
BareUseOutsidePackage { module: String, from: PathBuf },
/// `` use … of `relpath` `` matched no candidate file on disk.
#[error(
"cannot resolve `use … of `{relpath}`` from {}; searched: {}",
.from.display(),
format_searched(.searched)
)]
UnresolvedUseOf {
relpath: String,
from: PathBuf,
searched: Vec<PathBuf>,
},
/// A `use`-family header under Legacy mode (the mode that resolves
/// `@require:`/`@import:`). Names the fix.
#[error(
"{from}: `{header}` requires Envelopes mode (pass --deps, or let a \
`use` header pin it); this load ran in Legacy (@require/@import) mode"
)]
EnvelopeHeaderUnderLegacy { header: String, from: PathBuf },
/// A Legacy (`@require:`/`@import:`) header under Envelopes mode. Names
/// the fix.
#[error(
"{from}: `{header}` is a Legacy (@require/@import) header; Envelopes \
mode resolves `use package` / `use … of` headers only"
)]
LegacyHeaderUnderEnvelopes { header: String, from: PathBuf },
/// The `--deps` file (`rustyfi-deps.yaml`) could not be read.
/// Upstream `DepsConfigNotFound`, `depsConfig.ml:40-45`.
#[error("{path}: cannot read rustyfi-deps.yaml: {source}")]
DepsConfigNotFound {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// `rustyfi-deps.yaml` failed to decode or validate — either a
/// YAML/shape error from `serde_yaml` or one of the two non-structural
/// checks (`path` must be absolute; `used_as` must be an uppercased
/// identifier). Upstream `DepsConfigError` wrapping `YamlError`
/// (`depsConfig.ml:46-47`, `yamlDecoder.ml`); `message` carries a
/// dotted-path context string in the same spirit as upstream's
/// `show_yaml_context` (wording is this port's own).
#[error("{path}: invalid rustyfi-deps.yaml: {message}")]
DepsConfigDecode { path: PathBuf, message: String },
/// A `rustyfi-envelope.yaml` could not be read. Upstream
/// `EnvelopeConfigNotFound`, `envelopeConfig.ml:142-147`.
#[error("{path}: cannot read rustyfi-envelope.yaml: {source}")]
EnvelopeConfigNotFound {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// A `rustyfi-envelope.yaml` failed to decode or validate.
/// Upstream `EnvelopeConfigError`. Covers the `library`/`font` branch
/// check, `opentype_single`/`opentype_collection` branch check, relative
/// `path`, lowercased font `name`, and the 18 `markdown_conversion`
/// command/identifier shapes.
#[error("{path}: invalid rustyfi-envelope.yaml: {message}")]
EnvelopeConfigDecode { path: PathBuf, message: String },
/// A `source_directories` entry of an envelope could not be
/// listed. Upstream `CannotReadDirectory`, `envelopeReader.ml:15-16`.
#[error("{path}: cannot list envelope source directory: {source}")]
CannotReadDirectory {
path: PathBuf,
#[source]
source: std::io::Error,
},
/// Two deps-config envelopes share a name. Upstream
/// `EnvelopeNameConflict`, `closedEnvelopeDependencyResolver.ml:50-51`.
#[error("rustyfi-deps.yaml: two envelopes are named `{name}`")]
EnvelopeNameConflict { name: String },
/// An envelope depends on a name absent from the deps config's
/// envelope set. Upstream `DependencyOnUnknownEnvelope`,
/// `closedEnvelopeDependencyResolver.ml:71-76`.
#[error("envelope `{depending}` depends on unknown envelope `{depended}`")]
DependencyOnUnknownEnvelope { depending: String, depended: String },
/// A cycle in the envelope dependency graph. Upstream
/// `CyclicEnvelopeDependency`; `chain` is names (not paths), the first
/// element repeated last, matching [`LoadError::Cycle`]'s shape.
#[error("envelope dependency cycle: {}", .chain.join(" -> "))]
CyclicEnvelopeDependency { chain: Vec<String> },
/// Two source files in one envelope declare the same module
/// name. Upstream `FileModuleNameConflict`,
/// `closedFileDependencyResolver.ml:20-22`.
#[error(
"envelope module `{module}` is declared by both {} and {}",
.prev.display(),
.path.display()
)]
FileModuleNameConflict {
module: String,
prev: PathBuf,
path: PathBuf,
},
/// A bare `use M` inside an envelope names no sibling module.
/// Upstream `FileModuleNotFound`, `closedFileDependencyResolver.ml:37-41`.
#[error("{from}: `use {module}` names no module in this envelope")]
FileModuleNotFound { module: String, from: PathBuf },
/// A `use … of` header inside an envelope source tree. Upstream
/// `CannotUseHeaderUseOf`, `closedFileDependencyResolver.ml:51-52`.
#[error(
"{from}: `use {module} of …` is not allowed inside a package; package \
files address siblings by bare `use <Module>`"
)]
UseOfInsidePackage { module: String, from: PathBuf },
/// A `use package M` header whose head matches no `used_as` alias
/// in the supplied deps config. Upstream `UnknownPackageDependency`
/// (typecheck-side there, load-side here — this port's loader is the only
/// header consumer).
#[error(
"{from}: `use package {module}` does not match any dependency alias \
(`used_as`) in the supplied rustyfi-deps.yaml"
)]
UnknownPackageDependency { module: String, from: PathBuf },
}
fn format_versions(versions: &[RustyfiVersion]) -> String {
versions
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
}
fn format_searched(searched: &[PathBuf]) -> String {
if searched.is_empty() {
"(no candidates; is `lib_root` configured?)".to_string()
} else {
searched
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
}
}
fn format_chain(chain: &[PathBuf]) -> String {
chain
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(" -> ")
}