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
267
268
//! Typed failures returned while building a target or resolution snapshot.
//!
//! Authority refusals are returned before any source is read. Closure failures
//! carry the site that declared the step, the path that was attempted while it
//! remained inside the repository root, and one owning message.
use std::fmt;
/// Which configured ceiling a closure crossed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolutionLimit {
/// Resolution units one snapshot may hold.
Units,
/// Distinct source files one snapshot may hold.
SourceFiles,
/// Bytes one source file may hold.
SourceFileBytes,
/// Bytes all distinct source text may hold.
TotalSourceBytes,
/// Rust module nesting depth followed inside one unit.
ModuleDepth,
/// Rust module instances one unit may hold.
ModuleInstances,
/// Cargo dependency depth followed from the root target.
DependencyDepth,
/// Syntax nesting depth accepted while parsing.
SyntaxDepth,
}
impl fmt::Display for ResolutionLimit {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.field_name())
}
}
impl ResolutionLimit {
/// The `ResolutionLimits` field this ceiling belongs to.
fn field_name(&self) -> &'static str {
match self {
Self::Units => "max_units",
Self::SourceFiles => "max_source_files",
Self::SourceFileBytes => "max_source_file_bytes",
Self::TotalSourceBytes => "max_total_source_bytes",
Self::ModuleDepth => "max_module_depth",
Self::ModuleInstances => "max_module_instances",
Self::DependencyDepth => "max_dependency_depth",
Self::SyntaxDepth => "max_syntax_depth",
}
}
}
/// Which closure step failed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceClosureFailureKind {
/// A target's entry point could not be read.
EntryRead,
/// A declared module's source could not be read.
ModuleRead,
/// A reached source is not valid Rust.
SourceParse,
/// A `mod` declaration names no existing source.
MissingModule,
/// A `mod` declaration matches both `name.rs` and `name/mod.rs`.
AmbiguousModule,
/// An in-repository dependency package declares no library target.
MissingDependencyLibraryTarget,
/// An in-repository dependency names a library target the project that
/// issued it no longer holds.
UnresolvedDependencyLibraryTarget,
/// A path the closure already interned names no source in the store.
MissingStoredSource,
/// One package repeats inside a single dependency chain.
DependencyCycle,
/// A module path or symlink resolves outside the repository root.
OutOfRoot,
/// A reached source is not valid UTF-8.
InvalidUtf8,
/// A configured ceiling was crossed.
LimitExceeded(ResolutionLimit),
}
/// Where a closure failure was declared.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClosureSite {
/// A Cargo target's entry point.
Target {
/// The Cargo target name.
name: Box<str>,
/// The repository-relative entry path.
entry: Box<str>,
},
/// A `mod` declaration inside one source.
Module {
/// The repository-relative source that declares the module.
file: Box<str>,
/// The declared module name.
module: Box<str>,
},
/// A Cargo dependency edge.
Dependency {
/// The package that declares the edge.
package: Box<str>,
/// The namespace-local dependency name.
dependency: Box<str>,
},
}
impl ClosureSite {
/// The read-failure kind this site owns: a target owns its entry point,
/// and every other site reads a declared module.
pub(super) fn read_kind(&self) -> SourceClosureFailureKind {
match self {
Self::Target { .. } => SourceClosureFailureKind::EntryRead,
_ => SourceClosureFailureKind::ModuleRead,
}
}
}
impl fmt::Display for ClosureSite {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Target { name, entry } => write!(formatter, "target {name} entry {entry}"),
Self::Module { file, module } => write!(formatter, "{file} declares mod {module}"),
Self::Dependency {
package,
dependency,
} => write!(formatter, "{package} depends on {dependency}"),
}
}
}
/// One typed closure failure with its declaring site and attempted path.
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub struct SourceClosureFailure {
kind: SourceClosureFailureKind,
site: ClosureSite,
attempted: Option<Box<str>>,
message: Box<str>,
}
impl SourceClosureFailure {
/// Record one failure with the evidence its kind can carry.
pub(super) fn new(
kind: SourceClosureFailureKind,
evidence: (ClosureSite, Option<Box<str>>),
message: Box<str>,
) -> Self {
let (site, attempted) = evidence;
Self {
kind,
site,
attempted,
message,
}
}
/// Which closure step failed.
pub fn kind(&self) -> SourceClosureFailureKind {
self.kind
}
/// The site that declared the failed step. Every closure step is declared
/// by a target entry, a `mod` item, or a Cargo edge, so there is always one.
pub fn site(&self) -> &ClosureSite {
&self.site
}
/// The attempted repository-relative path, when it stayed inside the root.
pub fn attempted(&self) -> Option<&str> {
self.attempted.as_deref()
}
/// The single-owner reason this step failed.
pub fn message(&self) -> &str {
&self.message
}
/// Whether this failure ends the traversal rather than one branch of it.
///
/// A crossed ceiling makes every later step meaningless, so reporting the
/// same limit once per remaining source would bury the one real cause.
pub(super) fn is_fatal(&self) -> bool {
matches!(self.kind, SourceClosureFailureKind::LimitExceeded(_))
}
}
/// Every failure one closure attempt collected, beside the paths it did reach.
#[derive(Debug, thiserror::Error)]
#[error("source closure failed after reaching {} path(s): {}", .reached.len(), render(.failures))]
pub struct SourceClosureError {
reached: Box<[Box<str>]>,
failures: Box<[SourceClosureFailure]>,
}
impl SourceClosureError {
/// Bind sorted reached paths to the failures that stopped the closure.
pub(super) fn new(reached: Box<[Box<str>]>, failures: Box<[SourceClosureFailure]>) -> Self {
Self { reached, failures }
}
/// The sorted paths this attempt reached, as evidence only. They are not a
/// partial snapshot and must never be hashed as a complete closure.
pub fn reached(&self) -> &[Box<str>] {
&self.reached
}
/// Every typed failure this attempt collected.
pub fn failures(&self) -> &[SourceClosureFailure] {
&self.failures
}
}
fn render(failures: &[SourceClosureFailure]) -> String {
failures
.iter()
.map(|failure| failure.message.to_string())
.collect::<Vec<_>>()
.join("; ")
}
/// Failure returned by either snapshot operation.
#[derive(Debug, thiserror::Error)]
pub enum RustSnapshotError {
/// The package identity was issued by a project rooted somewhere else.
#[error("the package identity was issued by another repository root")]
ForeignPackage,
/// The package identity was issued for another revision of this repository.
#[error("the package identity was issued for another manifest revision")]
StalePackage,
/// The package identity names a local index this project never issued.
#[error("no package occupies local index {index}")]
UnknownPackage {
/// The absent local index.
index: u32,
},
/// The identity was issued by a project rooted somewhere else.
#[error("the target identity was issued by another repository root")]
ForeignTarget,
/// The identity was issued for another revision of this repository.
#[error("the target identity was issued for another manifest revision")]
StaleTarget,
/// The identity names a local index this project never issued.
#[error("no target occupies local index {index}")]
UnknownTarget {
/// The absent local index.
index: u32,
},
/// A participating manifest changed after the project was loaded.
#[error("manifest {manifest} changed since the project was loaded")]
ProjectManifestsChanged {
/// The repository-relative manifest that no longer matches.
manifest: Box<str>,
},
/// A participating manifest could not be read while its revision was
/// re-checked, so whether it still matches is unknown rather than false.
#[error("manifest {manifest} could not be re-read: {source}")]
ManifestUnreadable {
/// The repository-relative manifest that could not be read.
manifest: Box<str>,
/// The underlying I/O failure.
#[source]
source: std::io::Error,
},
/// The target's source closure could not be completed.
#[error(transparent)]
SourceClosure(#[from] SourceClosureError),
}