1use crate::common::report::prepare_report_message_and_divert;
20use thiserror::Error;
21use yash_env::Env;
22use yash_env::semantics::ExitStatus;
23use yash_env::semantics::Field;
24use yash_env::source::Location;
25use yash_env::source::pretty::{Report, ReportType, Span, SpanRole, add_span};
26#[cfg(doc)]
27use yash_env::system::Concurrent;
28use yash_env::system::Fcntl;
29use yash_env::system::Isatty;
30use yash_env::system::Write;
31use yash_env::variable::Scope::Global;
32
33#[derive(Clone, Debug, Eq, Error, PartialEq)]
35pub struct UnsetVariablesError<'a> {
36 pub name: &'a Field,
38 pub read_only_location: Location,
40}
41
42impl std::fmt::Display for UnsetVariablesError<'_> {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 let error = yash_env::variable::UnsetError {
45 name: &self.name.value,
46 read_only_location: &self.read_only_location,
47 };
48 error.fmt(f)
49 }
50}
51
52impl UnsetVariablesError<'_> {
53 pub fn to_report(&self) -> Report<'_> {
55 let mut report = Report::new();
56 report.r#type = ReportType::Error;
57 report.title = "cannot unset variable".into();
58 add_span(
59 &self.name.origin.code,
60 Span {
61 range: self.name.origin.byte_range(),
62 role: SpanRole::Primary {
63 label: format!("read-only variable `{}`", self.name).into(),
64 },
65 },
66 &mut report.snippets,
67 );
68 add_span(
69 &self.read_only_location.code,
70 Span {
71 range: self.read_only_location.byte_range(),
72 role: SpanRole::Supplementary {
73 label: format!("variable `{}` was made read-only here", self.name).into(),
74 },
75 },
76 &mut report.snippets,
77 );
78 report
79 }
80}
81
82impl<'a> From<&'a UnsetVariablesError<'_>> for Report<'a> {
83 #[inline]
84 fn from(error: &'a UnsetVariablesError<'_>) -> Self {
85 error.to_report()
86 }
87}
88
89pub fn unset_variables<'a, S>(
98 env: &mut Env<S>,
99 names: &'a [Field],
100) -> Vec<UnsetVariablesError<'a>> {
101 let mut errors = Vec::new();
102 for name in names {
103 match env.variables.unset(&name.value, Global) {
104 Ok(_) => (),
105 Err(error) => errors.push(UnsetVariablesError {
106 name,
107 read_only_location: error.read_only_location.clone(),
108 }),
109 }
110 }
111 errors
112}
113
114#[deprecated(
118 note = "use `merge_reports` and `prepare_report_message_and_divert` directly",
119 since = "0.11.0"
120)]
121#[must_use = "returned message should be printed"]
122pub fn unset_variables_error_message<S>(
123 env: &Env<S>,
124 errors: &[UnsetVariablesError],
125) -> (String, yash_env::semantics::Result)
126where
127 S: Fcntl + Isatty + Write,
128{
129 let mut report = Report::new();
130 report.r#type = ReportType::Error;
131 report.title = "cannot unset variable".into();
132 for error in errors {
133 add_span(
134 &error.name.origin.code,
135 Span {
136 range: error.name.origin.byte_range(),
137 role: SpanRole::Primary {
138 label: error.to_string().into(),
139 },
140 },
141 &mut report.snippets,
142 );
143 add_span(
144 &error.read_only_location.code,
145 Span {
146 range: error.read_only_location.byte_range(),
147 role: SpanRole::Supplementary {
148 label: format!("variable `{}` was made read-only here", error.name).into(),
149 },
150 },
151 &mut report.snippets,
152 );
153 }
154 prepare_report_message_and_divert(env, report)
155}
156
157#[deprecated(
162 note = "use `merge_reports` and `report_failure` directly",
163 since = "0.11.0"
164)]
165pub async fn report_variables_error<S>(
166 env: &mut Env<S>,
167 errors: &[UnsetVariablesError<'_>],
168) -> crate::Result
169where
170 S: Fcntl + Isatty + Write,
171{
172 #[allow(deprecated)]
173 let (message, divert) = unset_variables_error_message(env, errors);
174 env.system.print_error(&message).await;
175 crate::Result::with_exit_status_and_divert(ExitStatus::FAILURE, divert)
176}
177
178#[derive(Clone, Debug, Eq, Error, PartialEq)]
180#[error("cannot unset read-only function `{name}`")]
181pub struct UnsetFunctionsError<'a> {
182 pub name: &'a Field,
184 pub read_only_location: Location,
186}
187
188impl UnsetFunctionsError<'_> {
189 #[must_use]
191 pub fn to_report(&self) -> Report<'_> {
192 let mut report = Report::new();
193 report.r#type = ReportType::Error;
194 report.title = "cannot unset function".into();
195 add_span(
196 &self.name.origin.code,
197 Span {
198 range: self.name.origin.byte_range(),
199 role: SpanRole::Primary {
200 label: format!("read-only function `{}`", self.name).into(),
201 },
202 },
203 &mut report.snippets,
204 );
205 add_span(
206 &self.read_only_location.code,
207 Span {
208 range: self.read_only_location.byte_range(),
209 role: SpanRole::Supplementary {
210 label: format!("function `{}` was made read-only here", self.name).into(),
211 },
212 },
213 &mut report.snippets,
214 );
215 report
216 }
217}
218
219impl<'a> From<&'a UnsetFunctionsError<'_>> for Report<'a> {
220 #[inline]
221 fn from(error: &'a UnsetFunctionsError<'_>) -> Self {
222 error.to_report()
223 }
224}
225
226pub fn unset_functions<'a, S>(
233 env: &mut Env<S>,
234 names: &'a [Field],
235) -> Vec<UnsetFunctionsError<'a>> {
236 let mut errors = Vec::new();
237 for name in names {
238 match env.functions.unset(&name.value) {
239 Ok(_) => (),
240 Err(error) => errors.push(UnsetFunctionsError {
241 name,
242 read_only_location: error.existing.read_only_location.clone().unwrap(),
243 }),
244 }
245 }
246 errors
247}
248
249#[deprecated(
253 note = "use `merge_reports` and `prepare_report_message_and_divert` directly",
254 since = "0.11.0"
255)]
256#[must_use = "returned message should be printed"]
257pub fn unset_functions_error_message<S>(
258 env: &mut Env<S>,
259 errors: &[UnsetFunctionsError<'_>],
260) -> (String, yash_env::semantics::Result)
261where
262 S: Fcntl + Isatty + Write,
263{
264 let mut report = Report::new();
265 report.r#type = ReportType::Error;
266 report.title = "cannot unset function".into();
267 for error in errors {
268 add_span(
269 &error.name.origin.code,
270 Span {
271 range: error.name.origin.byte_range(),
272 role: SpanRole::Primary {
273 label: error.to_string().into(),
274 },
275 },
276 &mut report.snippets,
277 );
278 add_span(
279 &error.read_only_location.code,
280 Span {
281 range: error.read_only_location.byte_range(),
282 role: SpanRole::Supplementary {
283 label: format!("function `{}` was made read-only here", error.name).into(),
284 },
285 },
286 &mut report.snippets,
287 );
288 }
289 prepare_report_message_and_divert(env, report)
290}
291
292#[deprecated(
297 note = "use `merge_reports` and `report_failure` directly",
298 since = "0.11.0"
299)]
300pub async fn report_functions_error<S>(
301 env: &mut Env<S>,
302 errors: &[UnsetFunctionsError<'_>],
303) -> crate::Result
304where
305 S: Fcntl + Isatty + Write,
306{
307 #[allow(deprecated)]
308 let (message, divert) = unset_functions_error_message(env, errors);
309 env.system.print_error(&message).await;
310 crate::Result::with_exit_status_and_divert(ExitStatus::FAILURE, divert)
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use assert_matches::assert_matches;
317 use yash_env::function::Function;
318 use yash_env::source::Location;
319 use yash_env::variable::Value;
320
321 #[test]
322 fn unsetting_one_variable() {
323 let mut env = Env::new_virtual();
324 env.get_or_create_variable("foo", Global)
325 .assign("FOO", None)
326 .unwrap();
327 env.get_or_create_variable("bar", Global)
328 .assign("BAR", None)
329 .unwrap();
330 env.get_or_create_variable("baz", Global)
331 .assign("BAZ", None)
332 .unwrap();
333 let names = Field::dummies(["bar"]);
334
335 let errors = unset_variables(&mut env, &names);
336 assert_eq!(errors, []);
337 assert_eq!(
338 env.variables.get("foo").unwrap().value,
339 Some(Value::scalar("FOO")),
340 );
341 assert_eq!(env.variables.get("bar"), None);
342 assert_eq!(
343 env.variables.get("baz").unwrap().value,
344 Some(Value::scalar("BAZ")),
345 );
346 }
347
348 #[test]
349 fn unsetting_many_variables() {
350 let mut env = Env::new_virtual();
351 env.get_or_create_variable("foo", Global)
352 .assign("FOO", None)
353 .unwrap();
354 env.get_or_create_variable("bar", Global)
355 .assign("BAR", None)
356 .unwrap();
357 env.get_or_create_variable("baz", Global)
358 .assign("BAZ", None)
359 .unwrap();
360 let names = Field::dummies(["bar", "foo", "baz"]);
361
362 let errors = unset_variables(&mut env, &names);
363 assert_eq!(errors, []);
364 assert_eq!(env.variables.get("foo"), None);
365 assert_eq!(env.variables.get("bar"), None);
366 assert_eq!(env.variables.get("baz"), None);
367 }
368
369 #[test]
370 fn unsetting_readonly_variables() {
371 let mut env = Env::new_virtual();
372 let mut a = env.get_or_create_variable("a", Global);
373 a.assign("A", None).unwrap();
374 let mut b = env.get_or_create_variable("b", Global);
375 b.assign("B", None).unwrap();
376 let location_b = Location::dummy("readonly b");
377 b.make_read_only(location_b.clone());
378 let mut c = env.get_or_create_variable("c", Global);
379 c.assign("C", None).unwrap();
380 let location_c = Location::dummy("readonly c");
381 c.make_read_only(location_c.clone());
382 let mut d = env.get_or_create_variable("d", Global);
383 d.assign("D", None).unwrap();
384 let names = Field::dummies(["a", "b", "c", "d"]);
385
386 let errors = unset_variables(&mut env, &names);
387 assert_matches!(&errors[..], [e1, e2] => {
388 assert_eq!(e1.name, &Field::dummy("b"));
389 assert_eq!(e1.read_only_location, location_b);
390 assert_eq!(e2.name, &Field::dummy("c"));
391 assert_eq!(e2.read_only_location, location_c);
392 });
393 assert_eq!(env.variables.get("a"), None);
394 assert_eq!(
395 env.variables.get("b").unwrap().value,
396 Some(Value::scalar("B")),
397 );
398 assert_eq!(
399 env.variables.get("c").unwrap().value,
400 Some(Value::scalar("C")),
401 );
402 assert_eq!(env.variables.get("d"), None);
403 }
404
405 fn dummy_function<S>(name: &str) -> Function<S> {
406 let body = yash_env::test_helper::function::FunctionBodyStub::rc_dyn();
407 Function::new(name, body, Location::dummy(name))
408 }
409
410 #[test]
411 fn unsetting_one_function() {
412 let mut env = Env::new_virtual();
413 env.functions.define(dummy_function("foo")).unwrap();
414 env.functions.define(dummy_function("bar")).unwrap();
415 env.functions.define(dummy_function("baz")).unwrap();
416 let names = Field::dummies(["foo"]);
417
418 let errors = unset_functions(&mut env, &names);
419 assert_eq!(errors, []);
420 assert_eq!(env.functions.get("foo"), None);
421 assert_eq!(env.functions.get("bar").unwrap().name, "bar");
422 assert_eq!(env.functions.get("baz").unwrap().name, "baz");
423 }
424
425 #[test]
426 fn unsetting_many_functions() {
427 let mut env = Env::new_virtual();
428 env.functions.define(dummy_function("foo")).unwrap();
429 env.functions.define(dummy_function("bar")).unwrap();
430 env.functions.define(dummy_function("baz")).unwrap();
431 let names = Field::dummies(["bar", "foo", "baz"]);
432
433 let errors = unset_functions(&mut env, &names);
434 assert_eq!(errors, []);
435 assert_eq!(env.functions.get("foo"), None);
436 assert_eq!(env.functions.get("bar"), None);
437 assert_eq!(env.functions.get("baz"), None);
438 }
439
440 #[test]
441 fn unsetting_readonly_function() {
442 let mut env = Env::new_virtual();
443 env.functions.define(dummy_function("a")).unwrap();
444 let location_b = Location::dummy("readonly b");
445 env.functions
446 .define(dummy_function("b").make_read_only(location_b.clone()))
447 .unwrap();
448 let location_c = Location::dummy("readonly c");
449 env.functions
450 .define(dummy_function("c").make_read_only(location_c.clone()))
451 .unwrap();
452 env.functions.define(dummy_function("d")).unwrap();
453 let names = Field::dummies(["a", "b", "c", "d"]);
454
455 let errors = unset_functions(&mut env, &names);
456 assert_matches!(&errors[..], [e1, e2] => {
457 assert_eq!(e1.name, &Field::dummy("b"));
458 assert_eq!(e1.read_only_location, location_b);
459 assert_eq!(e2.name, &Field::dummy("c"));
460 assert_eq!(e2.read_only_location, location_c);
461 });
462 assert_eq!(env.functions.get("a"), None);
463 assert_eq!(env.functions.get("b").unwrap().name, "b");
464 assert_eq!(env.functions.get("c").unwrap().name, "c");
465 assert_eq!(env.functions.get("d"), None);
466 }
467
468 }