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