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