1use crate::Handle as _;
20use crate::Runtime;
21use crate::command::Command;
22use crate::expansion::Field;
23use crate::expansion::expand_word;
24use std::ops::ControlFlow::Continue;
25use std::rc::Rc;
26use yash_env::Env;
27use yash_env::function::DefineError;
28use yash_env::function::Function;
29use yash_env::function::FunctionBody;
30use yash_env::function::FunctionBodyObject;
31use yash_env::semantics::ExitStatus;
32use yash_env::semantics::Result;
33use yash_env::system::Isatty;
34use yash_env::system::concurrency::WriteAll;
35use yash_syntax::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
36use yash_syntax::syntax;
37
38#[derive(Debug)]
40#[repr(transparent)]
41pub(crate) struct BodyImpl(pub syntax::FullCompoundCommand);
42
43impl std::fmt::Display for BodyImpl {
44 #[inline]
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 self.0.fmt(f)
47 }
48}
49
50impl<S: Runtime + 'static> FunctionBody<S> for BodyImpl {
51 async fn execute(&self, env: &mut Env<S>) -> Result {
52 self.0.execute(env).await
53 }
54}
55
56impl<S: Runtime + 'static> Command<S> for syntax::FunctionDefinition {
67 async fn execute(&self, env: &mut Env<S>) -> Result {
68 define_function(env, self).await?;
69 env.apply_errexit()
70 }
71}
72
73async fn define_function<S: Runtime + 'static>(
74 env: &mut Env<S>,
75 def: &syntax::FunctionDefinition,
76) -> Result {
77 let Field {
79 value: name,
80 origin,
81 } = match expand_word(env, &def.name).await {
82 Ok((field, _exit_status)) => field,
83 Err(error) => return error.handle(env).await,
84 };
85
86 let body: Rc<syntax::FullCompoundCommand> = Rc::clone(&def.body);
88 let body = Rc::into_raw(body).cast::<BodyImpl>();
89 let body = unsafe { Rc::from_raw(body) };
92 let function = Function::new(name, body as Rc<dyn FunctionBodyObject<S>>, origin);
93
94 match env.functions.define(function) {
96 Ok(_) => {
97 env.exit_status = ExitStatus::SUCCESS;
98 }
99 Err(error) => {
100 report_define_error(env, &error).await;
101 env.exit_status = ExitStatus::ERROR;
102 }
103 }
104 Continue(())
105}
106
107async fn report_define_error<S>(env: &mut Env<S>, error: &DefineError<S>)
111where
112 S: Isatty + WriteAll,
113{
114 let mut report = Report::new();
115 report.r#type = ReportType::Error;
116 report.title = error.to_string().into();
117 report.snippets =
118 Snippet::with_primary_span(&error.new.origin, "failed function redefinition".into());
119
120 add_span(
121 &error.existing.origin.code,
122 Span {
123 range: error.existing.origin.byte_range(),
124 role: SpanRole::Supplementary {
125 label: "existing function was defined here".into(),
126 },
127 },
128 &mut report.snippets,
129 );
130
131 let read_only_location = error.existing.read_only_location.as_ref().unwrap();
132 add_span(
133 &read_only_location.code,
134 Span {
135 range: read_only_location.byte_range(),
136 role: SpanRole::Supplementary {
137 label: "existing function was made read-only here".into(),
138 },
139 },
140 &mut report.snippets,
141 );
142
143 yash_env::io::print_report(env, &report).await;
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use futures_util::FutureExt as _;
150 use std::ops::ControlFlow::Break;
151 use yash_env::VirtualSystem;
152 use yash_env::option::On;
153 use yash_env::option::Option::ErrExit;
154 use yash_env::semantics::Divert;
155 use yash_env::system::Concurrent;
156 use yash_env::test_helper::assert_stderr;
157 use yash_env::test_helper::function::FunctionBodyStub;
158 use yash_syntax::source::Location;
159
160 #[test]
161 fn function_definition_new() {
162 let mut env = Env::new_virtual();
163 env.exit_status = ExitStatus::ERROR;
164 let definition = syntax::FunctionDefinition {
165 has_keyword: false,
166 name: "foo".parse().unwrap(),
167 body: Rc::new("{ :; }".parse().unwrap()),
168 };
169
170 let result = definition.execute(&mut env).now_or_never().unwrap();
171 assert_eq!(result, Continue(()));
172 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
173 assert_eq!(env.functions.len(), 1);
174 let function = env.functions.get("foo").unwrap();
175 assert_eq!(function.name, "foo");
176 assert_eq!(function.origin, definition.name.location);
177 assert_eq!(function.read_only_location, None);
179 }
180
181 #[test]
182 fn function_definition_overwrite() {
183 let mut env = Env::new_virtual();
184 env.exit_status = ExitStatus::ERROR;
185 let function = Function {
186 name: "foo".to_string(),
187 body: FunctionBodyStub::rc_dyn(),
188 origin: Location::dummy("dummy"),
189 read_only_location: None,
190 };
191 env.functions.define(function).unwrap();
192 let definition = syntax::FunctionDefinition {
193 has_keyword: false,
194 name: "foo".parse().unwrap(),
195 body: Rc::new("( :; )".parse().unwrap()),
196 };
197
198 let result = definition.execute(&mut env).now_or_never().unwrap();
199 assert_eq!(result, Continue(()));
200 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
201 assert_eq!(env.functions.len(), 1);
202 let function = env.functions.get("foo").unwrap();
203 assert_eq!(function.name, "foo");
204 assert_eq!(function.origin, definition.name.location);
205 assert_eq!(function.read_only_location, None);
207 }
208
209 #[test]
210 fn function_definition_read_only() {
211 let system = VirtualSystem::new();
212 let state = Rc::clone(&system.state);
213 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
214 let function = Rc::new(Function {
215 name: "foo".to_string(),
216 body: FunctionBodyStub::rc_dyn(),
217 origin: Location::dummy("dummy"),
218 read_only_location: Some(Location::dummy("readonly")),
219 });
220 env.functions.define(Rc::clone(&function)).unwrap();
221 let definition = syntax::FunctionDefinition {
222 has_keyword: false,
223 name: "foo".parse().unwrap(),
224 body: Rc::new("( :; )".parse().unwrap()),
225 };
226
227 let result = definition.execute(&mut env).now_or_never().unwrap();
228 assert_eq!(result, Continue(()));
229 assert_eq!(env.exit_status, ExitStatus::ERROR);
230 assert_eq!(env.functions.len(), 1);
231 assert_eq!(env.functions.get("foo").unwrap(), &function);
232 assert_stderr(&state, |stderr| {
233 assert!(
234 stderr.contains("foo"),
235 "error message should contain function name: {stderr:?}"
236 )
237 });
238 }
239
240 #[test]
241 fn function_definition_name_expansion() {
242 let mut env = Env::new_virtual();
243 let definition = syntax::FunctionDefinition {
244 has_keyword: false,
245 name: r"\a".parse().unwrap(),
246 body: Rc::new("{ :; }".parse().unwrap()),
247 };
248
249 let result = definition.execute(&mut env).now_or_never().unwrap();
250 assert_eq!(result, Continue(()));
251 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
252 let names: Vec<&str> = env.functions.iter().map(|f| f.name.as_str()).collect();
253 assert_eq!(names, ["a"]);
254 }
255
256 #[test]
257 fn errexit_in_function_definition() {
258 let mut env = Env::new_virtual();
259 let function = Function {
260 name: "foo".to_string(),
261 body: FunctionBodyStub::rc_dyn(),
262 origin: Location::dummy("dummy"),
263 read_only_location: Some(Location::dummy("readonly")),
264 };
265 env.functions.define(function).unwrap();
266 let definition = syntax::FunctionDefinition {
267 has_keyword: false,
268 name: "foo".parse().unwrap(),
269 body: Rc::new("( :; )".parse().unwrap()),
270 };
271 env.options.set(ErrExit, On);
272
273 let result = definition.execute(&mut env).now_or_never().unwrap();
274 assert_eq!(result, Break(Divert::Exit(None)));
275 assert_eq!(env.exit_status, ExitStatus::ERROR);
276 }
277}