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