yash_semantics/command/
function_definition.rs1use crate::Handle;
20use crate::command::Command;
21use crate::expansion::Field;
22use crate::expansion::expand_word;
23use std::ops::ControlFlow::Continue;
24use std::rc::Rc;
25use yash_env::Env;
26use yash_env::function::DefineError;
27use yash_env::function::Function;
28use yash_env::semantics::ExitStatus;
29use yash_env::semantics::Result;
30use yash_syntax::source::pretty::Annotation;
31use yash_syntax::source::pretty::AnnotationType;
32use yash_syntax::source::pretty::Message;
33use yash_syntax::syntax;
34
35impl Command for syntax::FunctionDefinition {
46 async fn execute(&self, env: &mut Env) -> Result {
47 define_function(env, self).await?;
48 env.apply_errexit()
49 }
50}
51
52async fn define_function(env: &mut Env, def: &syntax::FunctionDefinition) -> Result {
53 let Field {
55 value: name,
56 origin,
57 } = match expand_word(env, &def.name).await {
58 Ok((field, _exit_status)) => field,
59 Err(error) => return error.handle(env).await,
60 };
61
62 let function = Function::new(name, Rc::clone(&def.body), origin);
64 match env.functions.define(function) {
65 Ok(_) => {
66 env.exit_status = ExitStatus::SUCCESS;
67 }
68 Err(error) => {
69 report_define_error(env, &error).await;
70 env.exit_status = ExitStatus::ERROR;
71 }
72 }
73 Continue(())
74}
75
76async fn report_define_error(env: &mut Env, error: &DefineError) {
80 let message = Message {
81 r#type: AnnotationType::Error,
82 title: error.to_string().into(),
83 annotations: vec![
84 Annotation::new(
85 AnnotationType::Error,
86 "failed function redefinition".into(),
87 &error.new.origin,
88 ),
89 Annotation::new(
90 AnnotationType::Info,
91 "existing function was defined here".into(),
92 &error.existing.origin,
93 ),
94 Annotation::new(
95 AnnotationType::Info,
96 "existing function was made read-only here".into(),
97 error.existing.read_only_location.as_ref().unwrap(),
98 ),
99 ],
100 footers: vec![],
101 };
102
103 yash_env::io::print_message(env, message).await;
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use futures_util::FutureExt;
110 use std::ops::ControlFlow::Break;
111 use yash_env::VirtualSystem;
112 use yash_env::option::On;
113 use yash_env::option::Option::ErrExit;
114 use yash_env::semantics::Divert;
115 use yash_env_test_helper::assert_stderr;
116 use yash_syntax::source::Location;
117
118 #[test]
119 fn function_definition_new() {
120 let mut env = Env::new_virtual();
121 env.exit_status = ExitStatus::ERROR;
122 let definition = syntax::FunctionDefinition {
123 has_keyword: false,
124 name: "foo".parse().unwrap(),
125 body: Rc::new("{ :; }".parse().unwrap()),
126 };
127
128 let result = definition.execute(&mut env).now_or_never().unwrap();
129 assert_eq!(result, Continue(()));
130 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
131 assert_eq!(env.functions.len(), 1);
132 let function = env.functions.get("foo").unwrap();
133 assert_eq!(function.name, "foo");
134 assert_eq!(function.origin, definition.name.location);
135 assert_eq!(function.body, definition.body);
136 assert_eq!(function.read_only_location, None);
137 }
138
139 #[test]
140 fn function_definition_overwrite() {
141 let mut env = Env::new_virtual();
142 env.exit_status = ExitStatus::ERROR;
143 let function = Function {
144 name: "foo".to_string(),
145 body: Rc::new("{ :; }".parse().unwrap()),
146 origin: Location::dummy("dummy"),
147 read_only_location: None,
148 };
149 env.functions.define(function).unwrap();
150 let definition = syntax::FunctionDefinition {
151 has_keyword: false,
152 name: "foo".parse().unwrap(),
153 body: Rc::new("( :; )".parse().unwrap()),
154 };
155
156 let result = definition.execute(&mut env).now_or_never().unwrap();
157 assert_eq!(result, Continue(()));
158 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
159 assert_eq!(env.functions.len(), 1);
160 let function = env.functions.get("foo").unwrap();
161 assert_eq!(function.name, "foo");
162 assert_eq!(function.origin, definition.name.location);
163 assert_eq!(function.body, definition.body);
164 assert_eq!(function.read_only_location, None);
165 }
166
167 #[test]
168 fn function_definition_read_only() {
169 let system = VirtualSystem::new();
170 let state = Rc::clone(&system.state);
171 let mut env = Env::with_system(Box::new(system));
172 let function = Rc::new(Function {
173 name: "foo".to_string(),
174 body: Rc::new("{ :; }".parse().unwrap()),
175 origin: Location::dummy("dummy"),
176 read_only_location: Some(Location::dummy("readonly")),
177 });
178 env.functions.define(Rc::clone(&function)).unwrap();
179 let definition = syntax::FunctionDefinition {
180 has_keyword: false,
181 name: "foo".parse().unwrap(),
182 body: Rc::new("( :; )".parse().unwrap()),
183 };
184
185 let result = definition.execute(&mut env).now_or_never().unwrap();
186 assert_eq!(result, Continue(()));
187 assert_eq!(env.exit_status, ExitStatus::ERROR);
188 assert_eq!(env.functions.len(), 1);
189 assert_eq!(env.functions.get("foo").unwrap(), &function);
190 assert_stderr(&state, |stderr| {
191 assert!(
192 stderr.contains("foo"),
193 "error message should contain function name: {stderr:?}"
194 )
195 });
196 }
197
198 #[test]
199 fn function_definition_name_expansion() {
200 let mut env = Env::new_virtual();
201 let definition = syntax::FunctionDefinition {
202 has_keyword: false,
203 name: r"\a".parse().unwrap(),
204 body: Rc::new("{ :; }".parse().unwrap()),
205 };
206
207 let result = definition.execute(&mut env).now_or_never().unwrap();
208 assert_eq!(result, Continue(()));
209 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
210 let names: Vec<&str> = env.functions.iter().map(|f| f.name.as_str()).collect();
211 assert_eq!(names, ["a"]);
212 }
213
214 #[test]
215 fn errexit_in_function_definition() {
216 let mut env = Env::new_virtual();
217 let function = Function {
218 name: "foo".to_string(),
219 body: Rc::new("{ :; }".parse().unwrap()),
220 origin: Location::dummy("dummy"),
221 read_only_location: Some(Location::dummy("readonly")),
222 };
223 env.functions.define(function).unwrap();
224 let definition = syntax::FunctionDefinition {
225 has_keyword: false,
226 name: "foo".parse().unwrap(),
227 body: Rc::new("( :; )".parse().unwrap()),
228 };
229 env.options.set(ErrExit, On);
230
231 let result = definition.execute(&mut env).now_or_never().unwrap();
232 assert_eq!(result, Break(Divert::Exit(None)));
233 assert_eq!(env.exit_status, ExitStatus::ERROR);
234 }
235}