yash_semantics/command/
function_definition.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementations of function definition semantics.
18
19use 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/// Wrapper type to implement `FunctionBody` for `FullCompoundCommand`.
36#[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
53/// Executes the function definition command.
54///
55/// First, the function name is [expanded](expand_word). If the expansion fails,
56/// the execution ends with a non-zero exit status. Next, the environment is
57/// examined for an existing function having the same name.  If there is such a
58/// function that is read-only, the execution ends with a non-zero exit status.
59/// Finally, the function definition is inserted into the environment, and the
60/// execution ends with an exit status of zero.
61///
62/// The `ErrExit` shell option is [applied](Env::apply_errexit) on error.
63impl 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    // Expand the function name
72    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    // Prepare the function instance
81    let body: Rc<syntax::FullCompoundCommand> = Rc::clone(&def.body);
82    let body = Rc::into_raw(body).cast::<BodyImpl>();
83    // Safety: `BodyImpl` is `#[repr(transparent)]` over `FullCompoundCommand`,
84    // so it's safe to transmute the content of the `Rc`.
85    let body = unsafe { Rc::from_raw(body) };
86    let function = Function::new(name, body as Rc<dyn FunctionBodyObject>, origin);
87
88    // Define the function
89    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
101/// Reports a function definition error.
102///
103/// This function assumes `error.existing.read_only_location.is_some()`.
104async 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        // Body equality with original AST cannot be compared directly due to BodyImpl wrapping.
168        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        // Body equality with original AST cannot be compared directly due to BodyImpl wrapping.
196        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}