1use crate::semantics::Field;
98#[cfg(doc)]
99use crate::Env;
100use itertools::Itertools;
101use std::borrow::Borrow;
102use std::collections::hash_map::Entry::{Occupied, Vacant};
103use std::collections::HashMap;
104use std::ffi::CString;
105use std::fmt::Write;
106use std::hash::Hash;
107use std::iter::FusedIterator;
108use thiserror::Error;
109use yash_syntax::source::Location;
110
111mod value;
112
113pub use self::value::QuotedValue;
114pub use self::value::Value::{self, Array, Scalar};
115
116mod quirk;
117
118pub use self::quirk::Expansion;
119pub use self::quirk::Quirk;
120
121mod main;
122
123pub use self::main::AssignError;
124pub use self::main::Variable;
125pub use self::main::VariableRefMut;
126
127mod constants;
128
129pub use self::constants::*;
131
132#[derive(Clone, Debug, Eq, PartialEq)]
133struct VariableInContext {
134 variable: Variable,
135 context_index: usize,
136}
137
138#[derive(Clone, Debug, Default, Eq, PartialEq)]
140pub struct PositionalParams {
141 pub values: Vec<String>,
143 pub last_modified_location: Option<Location>,
145}
146
147impl PositionalParams {
148 pub fn from_fields<I>(fields: I) -> Self
155 where
156 I: IntoIterator<Item = Field>,
157 {
158 let mut fields = fields.into_iter();
159 let last_modified_location = fields.next().map(|field| field.origin);
160 let values = fields.map(|field| field.value).collect();
161 Self {
162 values,
163 last_modified_location,
164 }
165 }
166}
167
168#[derive(Clone, Debug, Eq, PartialEq)]
176pub enum Context {
177 Regular { positional_params: PositionalParams },
183
184 Volatile,
189}
190
191impl Default for Context {
192 fn default() -> Self {
193 Context::Regular {
194 positional_params: Default::default(),
195 }
196 }
197}
198
199#[derive(Clone, Debug, Eq, PartialEq)]
203pub struct VariableSet {
204 all_variables: HashMap<String, Vec<VariableInContext>>,
213
214 contexts: Vec<Context>,
219}
220
221impl Default for VariableSet {
222 fn default() -> Self {
223 VariableSet {
224 all_variables: Default::default(),
225 contexts: vec![Context::default()],
226 }
227 }
228}
229
230#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub enum Scope {
236 Global,
237 Local,
238 Volatile,
239}
240
241#[derive(Clone, Debug, Eq, Error, PartialEq)]
243#[error("cannot unset read-only variable `{name}`")]
244pub struct UnsetError<'a> {
245 pub name: &'a str,
247 pub read_only_location: &'a Location,
249}
250
251#[derive(Clone, Debug)]
255pub struct Iter<'a> {
256 inner: std::collections::hash_map::Iter<'a, String, Vec<VariableInContext>>,
257 min_context_index: usize,
258}
259
260impl VariableSet {
261 #[must_use]
263 pub fn new() -> VariableSet {
264 Default::default()
265 }
266
267 #[must_use]
278 pub fn get<N>(&self, name: &N) -> Option<&Variable>
279 where
280 String: Borrow<N>,
281 N: Hash + Eq + ?Sized,
282 {
283 Some(&self.all_variables.get(name)?.last()?.variable)
284 }
285
286 fn index_of_topmost_regular_context(contexts: &[Context]) -> usize {
288 contexts
289 .iter()
290 .rposition(|context| matches!(context, Context::Regular { .. }))
291 .expect("base context has gone")
292 }
293
294 fn index_of_context(scope: Scope, contexts: &[Context]) -> usize {
296 match scope {
297 Scope::Global => 0,
298 Scope::Local => Self::index_of_topmost_regular_context(contexts),
299 Scope::Volatile => Self::index_of_topmost_regular_context(contexts) + 1,
300 }
301 }
302
303 #[must_use]
321 pub fn get_scoped<N>(&self, name: &N, scope: Scope) -> Option<&Variable>
322 where
323 String: Borrow<N>,
324 N: Hash + Eq + ?Sized,
325 {
326 let index = Self::index_of_context(scope, &self.contexts);
327 self.all_variables
328 .get(name)?
329 .last()
330 .filter(|vic| vic.context_index >= index)
331 .map(|vic| &vic.variable)
332 }
333
334 #[inline]
381 pub fn get_or_new<S: Into<String>>(&mut self, name: S, scope: Scope) -> VariableRefMut {
382 self.get_or_new_impl(name.into(), scope)
383 }
384
385 fn get_or_new_impl(&mut self, name: String, scope: Scope) -> VariableRefMut {
386 let stack = match self.all_variables.entry(name) {
387 Vacant(vacant) => vacant.insert(Vec::new()),
388 Occupied(occupied) => occupied.into_mut(),
389 };
390 let context_index = match scope {
391 Scope::Global => 0,
392 Scope::Local => Self::index_of_topmost_regular_context(&self.contexts),
393 Scope::Volatile => self.contexts.len() - 1,
394 };
395
396 match scope {
397 Scope::Global | Scope::Local => 'branch: {
398 let mut removed_volatile_variable = None;
399
400 while let Some(var) = stack.last_mut() {
404 if var.context_index < context_index {
405 break;
406 }
407 match self.contexts[var.context_index] {
408 Context::Regular { .. } => {
409 if let Some(removed_volatile_variable) = removed_volatile_variable {
410 var.variable = removed_volatile_variable;
411 }
412 break 'branch;
413 }
414 Context::Volatile => {
415 removed_volatile_variable.get_or_insert(stack.pop().unwrap().variable);
416 }
417 }
418 }
419
420 stack.push(VariableInContext {
421 variable: removed_volatile_variable.unwrap_or_default(),
422 context_index,
423 });
424 }
425
426 Scope::Volatile => {
427 assert_eq!(
428 self.contexts[context_index],
429 Context::Volatile,
430 "no volatile context to store the variable",
431 );
432 if let Some(var) = stack.last() {
433 if var.context_index != context_index {
434 stack.push(VariableInContext {
435 variable: var.variable.clone(),
436 context_index,
437 });
438 }
439 } else {
440 stack.push(VariableInContext {
441 variable: Variable::default(),
442 context_index,
443 });
444 }
445 }
446 }
447
448 VariableRefMut::from(&mut stack.last_mut().unwrap().variable)
449 }
450
451 #[cfg(test)]
453 fn assert_normalized(&self) {
454 for context in self.all_variables.values() {
455 for vars in context.windows(2) {
456 assert!(
457 vars[0].context_index < vars[1].context_index,
458 "invalid context index: {vars:?}",
459 );
460 }
461 if let Some(last) = context.last() {
462 assert!(
463 last.context_index < self.contexts.len(),
464 "invalid context index: {last:?}",
465 );
466 }
467 }
468 }
469
470 #[must_use]
479 pub fn get_scalar<N>(&self, name: &N) -> Option<&str>
480 where
481 String: Borrow<N>,
482 N: Hash + Eq + ?Sized,
483 {
484 fn inner(var: &Variable) -> Option<&str> {
485 match var.value.as_ref()? {
486 Scalar(value) => Some(value),
487 Array(_) => None,
488 }
489 }
490 inner(self.get(name)?)
491 }
492
493 pub fn unset<'a>(
518 &'a mut self,
519 name: &'a str,
520 scope: Scope,
521 ) -> Result<Option<Variable>, UnsetError<'a>> {
522 let Some(stack) = self.all_variables.get_mut(name) else {
523 return Ok(None);
524 };
525
526 let index = Self::index_of_context(scope, &self.contexts);
528
529 if let Some(read_only_position) = stack[index..]
544 .iter()
545 .rposition(|vic| vic.variable.is_read_only())
546 {
547 let read_only_index = index + read_only_position;
548 let read_only_location = &stack[read_only_index].variable.read_only_location;
549 return Err(UnsetError {
550 name,
551 read_only_location: read_only_location.as_ref().unwrap(),
552 });
553 }
554
555 Ok(stack.drain(index..).next_back().map(|vic| vic.variable))
556 }
557
558 pub fn iter(&self, scope: Scope) -> Iter {
572 Iter {
573 inner: self.all_variables.iter(),
574 min_context_index: Self::index_of_context(scope, &self.contexts),
575 }
576 }
577
578 #[must_use]
580 pub fn env_c_strings(&self) -> Vec<CString> {
581 self.all_variables
582 .iter()
583 .filter_map(|(name, vars)| {
584 let var = &vars.last()?.variable;
585 let value = var.value.as_ref().filter(|_| var.is_exported)?;
586 let mut result = name.clone();
587 result.push('=');
588 match value {
589 Scalar(value) => result.push_str(value),
590 Array(values) => write!(result, "{}", values.iter().format(":")).ok()?,
591 }
592 CString::new(result).ok()
594 })
595 .collect()
596 }
597
598 pub fn extend_env<I, K, V>(&mut self, vars: I)
607 where
608 I: IntoIterator<Item = (K, V)>,
609 K: Into<String>,
610 V: Into<String>,
611 {
612 for (name, value) in vars {
613 let mut var = self.get_or_new(name, Scope::Global);
614 if var.assign(value.into(), None).is_ok() {
615 var.export(true)
616 }
617 }
618 }
619
620 pub fn init(&mut self) {
639 const VARIABLES: &[(&str, &str)] = &[
640 (IFS, IFS_INITIAL_VALUE),
641 (OPTIND, OPTIND_INITIAL_VALUE),
642 (PS1, PS1_INITIAL_VALUE_NON_ROOT),
643 (PS2, PS2_INITIAL_VALUE),
644 (PS4, PS4_INITIAL_VALUE),
645 ];
646 for &(name, value) in VARIABLES {
647 self.get_or_new(name, Scope::Global)
648 .assign(value, None)
649 .ok();
650 }
651
652 self.get_or_new(LINENO, Scope::Global)
653 .set_quirk(Some(Quirk::LineNumber))
654 }
655
656 #[must_use]
665 pub fn positional_params(&self) -> &PositionalParams {
666 self.contexts
667 .iter()
668 .rev()
669 .find_map(|context| match context {
670 Context::Regular { positional_params } => Some(positional_params),
671 Context::Volatile => None,
672 })
673 .expect("base context has gone")
674 }
675
676 #[must_use]
683 pub fn positional_params_mut(&mut self) -> &mut PositionalParams {
684 self.contexts
685 .iter_mut()
686 .rev()
687 .find_map(|context| match context {
688 Context::Regular { positional_params } => Some(positional_params),
689 Context::Volatile => None,
690 })
691 .expect("base context has gone")
692 }
693
694 fn push_context_impl(&mut self, context: Context) {
695 self.contexts.push(context);
696 }
697
698 fn pop_context_impl(&mut self) {
699 debug_assert!(!self.contexts.is_empty());
700 assert_ne!(self.contexts.len(), 1, "cannot pop the base context");
701 self.contexts.pop();
702 self.all_variables.retain(|_, stack| {
703 if let Some(vic) = stack.last() {
704 if vic.context_index >= self.contexts.len() {
705 stack.pop();
706 }
707 }
708 !stack.is_empty()
709 })
710 }
711}
712
713impl<'a> Iterator for Iter<'a> {
714 type Item = (&'a str, &'a Variable);
715
716 fn next(&mut self) -> Option<(&'a str, &'a Variable)> {
717 loop {
718 let next = self.inner.next()?;
719 if let Some(variable) = next.1.last() {
720 if variable.context_index >= self.min_context_index {
721 return Some((next.0, &variable.variable));
722 }
723 }
724 }
725 }
726
727 fn size_hint(&self) -> (usize, Option<usize>) {
728 let (_min, max) = self.inner.size_hint();
729 (0, max)
730 }
731}
732
733impl FusedIterator for Iter<'_> {}
734
735mod guard;
736
737pub use self::guard::{ContextGuard, EnvContextGuard};
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn new_variable_in_global_scope() {
745 let mut set = VariableSet::new();
746 set.push_context_impl(Context::default());
747 set.push_context_impl(Context::Volatile);
748
749 let mut var = set.get_or_new("foo", Scope::Global);
750
751 assert_eq!(*var, Variable::default());
752 var.assign("VALUE", None).unwrap();
753 set.assert_normalized();
754 set.pop_context_impl();
755 set.pop_context_impl();
756 assert_eq!(set.get("foo").unwrap().value, Some("VALUE".into()));
758 }
759
760 #[test]
761 fn existing_variable_in_global_scope() {
762 let mut set = VariableSet::new();
763 let mut var = set.get_or_new("foo", Scope::Global);
764 var.assign("ONE", None).unwrap();
765 set.push_context_impl(Context::default());
766 set.push_context_impl(Context::Volatile);
767
768 let mut var = set.get_or_new("foo", Scope::Global);
769
770 assert_eq!(var.value, Some("ONE".into()));
771 var.assign("TWO", Location::dummy("somewhere")).unwrap();
772 set.assert_normalized();
773 set.pop_context_impl();
774 set.pop_context_impl();
775 let var = set.get("foo").unwrap();
777 assert_eq!(var.value, Some("TWO".into()));
778 assert_eq!(
779 var.last_assigned_location,
780 Some(Location::dummy("somewhere")),
781 );
782 }
783
784 #[test]
785 fn new_variable_in_local_scope() {
786 let mut set = VariableSet::new();
788 set.push_context_impl(Context::default());
789
790 let mut var = set.get_or_new("foo", Scope::Local);
791
792 assert_eq!(*var, Variable::default());
793
794 var.assign("OUTER", None).unwrap();
795 set.push_context_impl(Context::default());
796 set.push_context_impl(Context::Volatile);
797
798 let mut var = set.get_or_new("foo", Scope::Local);
799
800 assert_eq!(*var, Variable::default());
801 var.assign("INNER", Location::dummy("location")).unwrap();
802 set.assert_normalized();
803 set.pop_context_impl(); assert_eq!(set.get("foo").unwrap().value, Some("INNER".into()));
805 set.pop_context_impl(); assert_eq!(set.get("foo").unwrap().value, Some("OUTER".into()));
807 set.pop_context_impl(); assert_eq!(set.get("foo"), None);
809 }
810
811 #[test]
812 fn existing_variable_in_local_scope() {
813 let mut set = VariableSet::new();
814 set.push_context_impl(Context::default());
815 let mut var = set.get_or_new("foo", Scope::Local);
816 var.assign("OLD", None).unwrap();
817
818 let mut var = set.get_or_new("foo", Scope::Local);
819
820 assert_eq!(var.value, Some("OLD".into()));
821 var.assign("NEW", None).unwrap();
822 assert_eq!(set.get("foo").unwrap().value, Some("NEW".into()));
823 set.assert_normalized();
824 set.pop_context_impl();
825 assert_eq!(set.get("foo"), None);
826 }
827
828 #[test]
829 fn new_variable_in_volatile_scope() {
830 let mut set = VariableSet::new();
831 set.push_context_impl(Context::Volatile);
832 set.push_context_impl(Context::Volatile);
833
834 let mut var = set.get_or_new("foo", Scope::Volatile);
835
836 assert_eq!(*var, Variable::default());
837 var.assign("VOLATILE", None).unwrap();
838 assert_eq!(set.get("foo").unwrap().value, Some("VOLATILE".into()));
839 set.assert_normalized();
840 set.pop_context_impl();
841 assert_eq!(set.get("foo"), None);
842 }
843
844 #[test]
845 fn cloning_existing_regular_variable_to_volatile_context() {
846 let mut set = VariableSet::new();
847 let mut var = set.get_or_new("foo", Scope::Global);
848 var.assign("VALUE", None).unwrap();
849 var.make_read_only(Location::dummy("read-only location"));
850 let save_var = var.clone();
851 set.push_context_impl(Context::Volatile);
852 set.push_context_impl(Context::Volatile);
853
854 let mut var = set.get_or_new("foo", Scope::Volatile);
855
856 assert_eq!(*var, save_var);
857 var.export(true);
858 assert!(set.get("foo").unwrap().is_exported);
859 set.assert_normalized();
860 set.pop_context_impl();
861 assert_eq!(set.get("foo"), Some(&save_var));
864 }
865
866 #[test]
867 fn existing_variable_in_volatile_scope() {
868 let mut set = VariableSet::new();
869 set.push_context_impl(Context::Volatile);
870 let mut var = set.get_or_new("foo", Scope::Volatile);
871 var.assign("INITIAL", None).unwrap();
872
873 let mut var = set.get_or_new("foo", Scope::Volatile);
874
875 assert_eq!(var.value, Some("INITIAL".into()));
876 var.assign(Value::array(["MODIFIED"]), Location::dummy("somewhere"))
877 .unwrap();
878 assert_eq!(
879 set.get("foo").unwrap().value,
880 Some(Value::array(["MODIFIED"])),
881 );
882 set.assert_normalized();
883 set.pop_context_impl();
884 assert_eq!(set.get("foo"), None);
885 }
886
887 #[test]
888 fn lowering_volatile_variable_to_base_context() {
889 let mut set = VariableSet::new();
890 set.push_context_impl(Context::default());
891 set.push_context_impl(Context::Volatile);
892 let mut var = set.get_or_new("foo", Scope::Volatile);
893 var.assign("DUMMY", None).unwrap();
894 set.push_context_impl(Context::Volatile);
895 let mut var = set.get_or_new("foo", Scope::Volatile);
896 var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
897 var.export(true);
898
899 let mut var = set.get_or_new("foo", Scope::Global);
900
901 assert_eq!(var.value, Some("VOLATILE".into()));
902 assert_eq!(
903 var.last_assigned_location,
904 Some(Location::dummy("anywhere")),
905 );
906 var.assign("NEW", Location::dummy("somewhere")).unwrap();
907 set.assert_normalized();
908 set.pop_context_impl();
909 set.pop_context_impl();
910 set.pop_context_impl();
911 let var = set.get("foo").unwrap();
914 assert_eq!(var.value, Some("NEW".into()));
915 assert_eq!(
916 var.last_assigned_location,
917 Some(Location::dummy("somewhere")),
918 );
919 assert!(var.is_exported);
921 }
922
923 #[test]
924 fn lowering_volatile_variable_to_middle_regular_context() {
925 let mut set = VariableSet::new();
926 let mut var = set.get_or_new("foo", Scope::Local);
927 var.assign("ONE", None).unwrap();
928 set.push_context_impl(Context::default());
929 let mut var = set.get_or_new("foo", Scope::Local);
930 var.assign("TWO", None).unwrap();
931 set.push_context_impl(Context::default());
932 set.push_context_impl(Context::Volatile);
933 let mut var = set.get_or_new("foo", Scope::Volatile);
934 var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
935 var.export(true);
936
937 let mut var = set.get_or_new("foo", Scope::Global);
938
939 assert_eq!(var.value, Some("VOLATILE".into()));
940 assert_eq!(
941 var.last_assigned_location,
942 Some(Location::dummy("anywhere")),
943 );
944 var.assign("NEW", Location::dummy("somewhere")).unwrap();
945 set.assert_normalized();
946 set.pop_context_impl();
947 set.pop_context_impl();
948 let var = set.get("foo").unwrap();
950 assert_eq!(var.value, Some("NEW".into()));
951 assert_eq!(
952 var.last_assigned_location,
953 Some(Location::dummy("somewhere")),
954 );
955 assert!(var.is_exported);
957 set.pop_context_impl();
958 let var = set.get("foo").unwrap();
960 assert_eq!(var.value, Some("ONE".into()));
961 }
962
963 #[test]
964 fn lowering_volatile_variable_to_topmost_regular_context_without_existing_variable() {
965 let mut set = VariableSet::new();
966 set.push_context_impl(Context::default());
967 set.push_context_impl(Context::default());
968 set.push_context_impl(Context::Volatile);
969 let mut var = set.get_or_new("foo", Scope::Volatile);
970 var.assign("DUMMY", None).unwrap();
971 set.push_context_impl(Context::Volatile);
972 let mut var = set.get_or_new("foo", Scope::Volatile);
973 var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
974 var.export(true);
975
976 let mut var = set.get_or_new("foo", Scope::Local);
977
978 assert_eq!(var.value, Some("VOLATILE".into()));
979 assert_eq!(
980 var.last_assigned_location,
981 Some(Location::dummy("anywhere")),
982 );
983 var.assign("NEW", Location::dummy("somewhere")).unwrap();
984 set.assert_normalized();
985 set.pop_context_impl();
986 set.pop_context_impl();
987 let var = set.get("foo").unwrap();
990 assert_eq!(var.value, Some("NEW".into()));
991 assert_eq!(
992 var.last_assigned_location,
993 Some(Location::dummy("somewhere")),
994 );
995 assert!(var.is_exported);
997 }
998
999 #[test]
1000 fn lowering_volatile_variable_to_topmost_regular_context_overwriting_existing_variable() {
1001 let mut set = VariableSet::new();
1002 set.push_context_impl(Context::default());
1003 set.push_context_impl(Context::default());
1004 let mut var = set.get_or_new("foo", Scope::Local);
1005 var.assign("OLD", None).unwrap();
1006 set.push_context_impl(Context::Volatile);
1007 let mut var = set.get_or_new("foo", Scope::Volatile);
1008 var.assign("DUMMY", None).unwrap();
1009 set.push_context_impl(Context::Volatile);
1010 let mut var = set.get_or_new("foo", Scope::Volatile);
1011 var.assign("VOLATILE", Location::dummy("first")).unwrap();
1012 var.export(true);
1013 set.push_context_impl(Context::Volatile);
1014
1015 let mut var = set.get_or_new("foo", Scope::Local);
1016
1017 assert_eq!(var.value, Some("VOLATILE".into()));
1018 assert_eq!(var.last_assigned_location, Some(Location::dummy("first")));
1019 var.assign("NEW", Location::dummy("second")).unwrap();
1020 set.assert_normalized();
1021 set.pop_context_impl();
1022 set.pop_context_impl();
1023 set.pop_context_impl();
1024 let var = set.get("foo").unwrap();
1027 assert_eq!(var.value, Some("NEW".into()));
1028 assert_eq!(var.last_assigned_location, Some(Location::dummy("second")));
1029 assert!(var.is_exported);
1031 }
1032
1033 #[test]
1034 #[should_panic(expected = "no volatile context to store the variable")]
1035 fn missing_volatile_context() {
1036 let mut set = VariableSet::new();
1037 set.get_or_new("foo", Scope::Volatile);
1038 }
1039
1040 #[test]
1041 fn getting_variables_with_scopes() {
1042 let mut set = VariableSet::new();
1043 set.get_or_new("global", Scope::Global)
1044 .assign("G", None)
1045 .unwrap();
1046 set.push_context_impl(Context::default());
1047 set.get_or_new("local", Scope::Local)
1048 .assign("L", None)
1049 .unwrap();
1050 set.push_context_impl(Context::Volatile);
1051 set.get_or_new("volatile", Scope::Volatile)
1052 .assign("V", None)
1053 .unwrap();
1054
1055 assert_eq!(
1056 set.get_scoped("global", Scope::Global),
1057 Some(&Variable::new("G")),
1058 );
1059 assert_eq!(set.get_scoped("global", Scope::Local), None);
1060 assert_eq!(set.get_scoped("global", Scope::Volatile), None);
1061
1062 assert_eq!(
1063 set.get_scoped("local", Scope::Global),
1064 Some(&Variable::new("L"))
1065 );
1066 assert_eq!(
1067 set.get_scoped("local", Scope::Local),
1068 Some(&Variable::new("L"))
1069 );
1070 assert_eq!(set.get_scoped("local", Scope::Volatile), None);
1071
1072 assert_eq!(
1073 set.get_scoped("volatile", Scope::Global),
1074 Some(&Variable::new("V"))
1075 );
1076 assert_eq!(
1077 set.get_scoped("volatile", Scope::Local),
1078 Some(&Variable::new("V"))
1079 );
1080 assert_eq!(
1081 set.get_scoped("volatile", Scope::Volatile),
1082 Some(&Variable::new("V"))
1083 );
1084 }
1085
1086 #[test]
1087 fn unsetting_nonexisting_variable() {
1088 let mut variables = VariableSet::new();
1089 let result = variables.unset("", Scope::Global).unwrap();
1090 assert_eq!(result, None);
1091 }
1092
1093 #[test]
1094 fn unsetting_variable_with_one_context() {
1095 let mut variables = VariableSet::new();
1096 variables
1097 .get_or_new("foo", Scope::Global)
1098 .assign("X", None)
1099 .unwrap();
1100
1101 let result = variables.unset("foo", Scope::Global).unwrap();
1102 assert_eq!(result, Some(Variable::new("X")));
1103 assert_eq!(variables.get("foo"), None);
1104 }
1105
1106 #[test]
1107 fn unsetting_variables_from_all_contexts() {
1108 let mut variables = VariableSet::new();
1109 variables
1110 .get_or_new("foo", Scope::Global)
1111 .assign("X", None)
1112 .unwrap();
1113 variables.push_context_impl(Context::default());
1114 variables
1115 .get_or_new("foo", Scope::Local)
1116 .assign("Y", None)
1117 .unwrap();
1118 variables.push_context_impl(Context::Volatile);
1119 variables
1120 .get_or_new("foo", Scope::Volatile)
1121 .assign("Z", None)
1122 .unwrap();
1123
1124 let result = variables.unset("foo", Scope::Global).unwrap();
1125 assert_eq!(result, Some(Variable::new("Z")));
1126 assert_eq!(variables.get("foo"), None);
1127 }
1128
1129 #[test]
1130 fn unsetting_variable_from_local_context() {
1131 let mut variables = VariableSet::new();
1132 variables
1133 .get_or_new("foo", Scope::Global)
1134 .assign("A", None)
1135 .unwrap();
1136 variables.push_context_impl(Context::default());
1137 let mut readonly_foo = variables.get_or_new("foo", Scope::Local);
1139 readonly_foo.assign("B", None).unwrap();
1140 readonly_foo.make_read_only(Location::dummy("dummy"));
1141 let readonly_foo = readonly_foo.clone();
1142 variables.push_context_impl(Context::default());
1143 variables
1144 .get_or_new("foo", Scope::Local)
1145 .assign("C", None)
1146 .unwrap();
1147 variables.push_context_impl(Context::Volatile);
1148 variables
1149 .get_or_new("foo", Scope::Volatile)
1150 .assign("D", None)
1151 .unwrap();
1152
1153 let result = variables.unset("foo", Scope::Local).unwrap();
1154 assert_eq!(result, Some(Variable::new("D")));
1155 assert_eq!(variables.get("foo"), Some(&readonly_foo));
1156 }
1157
1158 #[test]
1159 fn unsetting_nonexisting_variable_in_local_context() {
1160 let mut variables = VariableSet::new();
1161 variables
1162 .get_or_new("foo", Scope::Global)
1163 .assign("A", None)
1164 .unwrap();
1165 variables.push_context_impl(Context::default());
1166
1167 let result = variables.unset("foo", Scope::Local).unwrap();
1168 assert_eq!(result, None);
1169 assert_eq!(variables.get("foo"), Some(&Variable::new("A")));
1170 }
1171
1172 #[test]
1173 fn unsetting_variable_from_volatile_context() {
1174 let mut variables = VariableSet::new();
1175 variables
1176 .get_or_new("foo", Scope::Global)
1177 .assign("A", None)
1178 .unwrap();
1179 variables.push_context_impl(Context::default());
1180 variables
1181 .get_or_new("foo", Scope::Local)
1182 .assign("B", None)
1183 .unwrap();
1184 variables.push_context_impl(Context::Volatile);
1185 variables
1186 .get_or_new("foo", Scope::Volatile)
1187 .assign("C", None)
1188 .unwrap();
1189 variables.push_context_impl(Context::Volatile);
1190 variables
1191 .get_or_new("foo", Scope::Volatile)
1192 .assign("D", None)
1193 .unwrap();
1194
1195 let result = variables.unset("foo", Scope::Volatile).unwrap();
1196 assert_eq!(result, Some(Variable::new("D")));
1197 assert_eq!(variables.get("foo"), Some(&Variable::new("B")));
1198 }
1199
1200 #[test]
1201 fn unsetting_nonexisting_variable_in_volatile_context() {
1202 let mut variables = VariableSet::new();
1203 variables
1204 .get_or_new("foo", Scope::Global)
1205 .assign("A", None)
1206 .unwrap();
1207 variables.push_context_impl(Context::Volatile);
1208
1209 let result = variables.unset("foo", Scope::Volatile).unwrap();
1210 assert_eq!(result, None);
1211 assert_eq!(variables.get("foo"), Some(&Variable::new("A")));
1212 }
1213
1214 #[test]
1215 fn unsetting_readonly_variable() {
1216 let read_only_location = &Location::dummy("read-only");
1217 let mut variables = VariableSet::new();
1218 let mut foo = variables.get_or_new("foo", Scope::Global);
1219 foo.assign("A", None).unwrap();
1220 variables.push_context_impl(Context::default());
1221 let mut foo = variables.get_or_new("foo", Scope::Local);
1222 foo.assign("B", None).unwrap();
1223 foo.make_read_only(Location::dummy("dummy"));
1224 variables.push_context_impl(Context::default());
1225 let mut foo = variables.get_or_new("foo", Scope::Local);
1226 foo.assign("C", None).unwrap();
1227 foo.make_read_only(read_only_location.clone());
1228 variables.push_context_impl(Context::default());
1229 let mut foo = variables.get_or_new("foo", Scope::Local);
1230 foo.assign("D", None).unwrap();
1231
1232 let error = variables.unset("foo", Scope::Global).unwrap_err();
1233 assert_eq!(
1234 error,
1235 UnsetError {
1236 name: "foo",
1237 read_only_location
1238 }
1239 );
1240 assert_eq!(variables.get("foo"), Some(&Variable::new("D")));
1241 }
1242
1243 #[test]
1244 #[should_panic(expected = "cannot pop the base context")]
1245 fn cannot_pop_base_context() {
1246 let mut variables = VariableSet::new();
1247 variables.pop_context_impl();
1248 }
1249
1250 fn test_iter<F: FnOnce(&VariableSet)>(f: F) {
1251 let mut set = VariableSet::new();
1252
1253 let mut var = set.get_or_new("global", Scope::Global);
1254 var.assign("global value", None).unwrap();
1255 var.export(true);
1256 let mut var = set.get_or_new("local", Scope::Global);
1257 var.assign("hidden value", None).unwrap();
1258
1259 let mut set = set.push_context(Context::default());
1260
1261 let mut var = set.get_or_new("local", Scope::Local);
1262 var.assign("visible value", None).unwrap();
1263 let mut var = set.get_or_new("volatile", Scope::Local);
1264 var.assign("hidden value", None).unwrap();
1265
1266 let mut set = set.push_context(Context::Volatile);
1267
1268 let mut var = set.get_or_new("volatile", Scope::Volatile);
1269 var.assign("volatile value", None).unwrap();
1270
1271 f(&mut set);
1272 }
1273
1274 #[test]
1275 fn iter_global() {
1276 test_iter(|set| {
1277 let mut v: Vec<_> = set.iter(Scope::Global).collect();
1278 v.sort_unstable_by_key(|&(name, _)| name);
1279 assert_eq!(
1280 v,
1281 [
1282 ("global", &Variable::new("global value").export()),
1283 ("local", &Variable::new("visible value")),
1284 ("volatile", &Variable::new("volatile value"))
1285 ]
1286 );
1287 })
1288 }
1289
1290 #[test]
1291 fn iter_local() {
1292 test_iter(|set| {
1293 let mut v: Vec<_> = set.iter(Scope::Local).collect();
1294 v.sort_unstable_by_key(|&(name, _)| name);
1295 assert_eq!(
1296 v,
1297 [
1298 ("local", &Variable::new("visible value")),
1299 ("volatile", &Variable::new("volatile value"))
1300 ]
1301 );
1302 })
1303 }
1304
1305 #[test]
1306 fn iter_volatile() {
1307 test_iter(|set| {
1308 let mut v: Vec<_> = set.iter(Scope::Volatile).collect();
1309 v.sort_unstable_by_key(|&(name, _)| name);
1310 assert_eq!(v, [("volatile", &Variable::new("volatile value"))]);
1311 })
1312 }
1313
1314 #[test]
1315 fn iter_size_hint() {
1316 test_iter(|set| {
1317 assert_eq!(set.iter(Scope::Global).size_hint(), (0, Some(3)));
1318 assert_eq!(set.iter(Scope::Local).size_hint(), (0, Some(3)));
1319 assert_eq!(set.iter(Scope::Volatile).size_hint(), (0, Some(3)));
1320 })
1321 }
1322
1323 #[test]
1324 fn env_c_strings() {
1325 let mut variables = VariableSet::new();
1326 assert_eq!(&variables.env_c_strings(), &[]);
1327
1328 let mut var = variables.get_or_new("foo", Scope::Global);
1329 var.assign("FOO", None).unwrap();
1330 var.export(true);
1331 let mut var = variables.get_or_new("bar", Scope::Global);
1332 var.assign(Value::array(["BAR"]), None).unwrap();
1333 var.export(true);
1334 let mut var = variables.get_or_new("baz", Scope::Global);
1335 var.assign(Value::array(["1", "two", "3"]), None).unwrap();
1336 var.export(true);
1337 let mut var = variables.get_or_new("null", Scope::Global);
1338 var.assign("not exported", None).unwrap();
1339 variables.get_or_new("none", Scope::Global);
1340
1341 let mut ss = variables.env_c_strings();
1342 ss.sort_unstable();
1343 assert_eq!(
1344 &ss,
1345 &[
1346 c"bar=BAR".to_owned(),
1347 c"baz=1:two:3".to_owned(),
1348 c"foo=FOO".to_owned()
1349 ]
1350 );
1351 }
1352
1353 #[test]
1354 fn extend_env() {
1355 let mut variables = VariableSet::new();
1356
1357 variables.extend_env([("foo", "FOO"), ("bar", "OK")]);
1358
1359 let foo = variables.get("foo").unwrap();
1360 assert_eq!(foo.value, Some("FOO".into()));
1361 assert!(foo.is_exported);
1362 let bar = variables.get("bar").unwrap();
1363 assert_eq!(bar.value, Some("OK".into()));
1364 assert!(bar.is_exported);
1365 }
1366
1367 #[test]
1368 fn init_lineno() {
1369 let mut variables = VariableSet::new();
1370 variables.init();
1371 let v = variables.get(LINENO).unwrap();
1372 assert_eq!(v.value, None);
1373 assert_eq!(v.quirk, Some(Quirk::LineNumber));
1374 assert_eq!(v.last_assigned_location, None);
1375 assert!(!v.is_exported);
1376 assert_eq!(v.read_only_location, None);
1377 }
1378
1379 #[test]
1380 fn positional_params_in_base_context() {
1381 let mut variables = VariableSet::new();
1382 assert_eq!(variables.positional_params().values, [] as [String; 0]);
1383
1384 let params = variables.positional_params_mut();
1385 params.values.push("foo".to_string());
1386 params.values.push("bar".to_string());
1387
1388 assert_eq!(
1389 variables.positional_params().values,
1390 ["foo".to_string(), "bar".to_string()],
1391 );
1392 }
1393
1394 #[test]
1395 fn positional_params_in_second_regular_context() {
1396 let mut variables = VariableSet::new();
1397 variables.push_context_impl(Context::default());
1398 assert_eq!(variables.positional_params().values, [] as [String; 0]);
1399
1400 let params = variables.positional_params_mut();
1401 params.values.push("1".to_string());
1402
1403 assert_eq!(variables.positional_params().values, ["1".to_string()]);
1404 }
1405
1406 #[test]
1407 fn getting_positional_params_in_volatile_context() {
1408 let mut variables = VariableSet::new();
1409
1410 let params = variables.positional_params_mut();
1411 params.values.push("a".to_string());
1412 params.values.push("b".to_string());
1413 params.values.push("c".to_string());
1414
1415 variables.push_context_impl(Context::Volatile);
1416 assert_eq!(
1417 variables.positional_params().values,
1418 ["a".to_string(), "b".to_string(), "c".to_string()],
1419 );
1420 }
1421
1422 #[test]
1423 fn setting_positional_params_in_volatile_context() {
1424 let mut variables = VariableSet::new();
1425 variables.push_context_impl(Context::Volatile);
1426
1427 let params = variables.positional_params_mut();
1428 params.values.push("x".to_string());
1429
1430 variables.pop_context_impl();
1431 assert_eq!(variables.positional_params().values, ["x".to_string()]);
1432 }
1433}