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::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/// Wrapper type to implement `FunctionBody` for `FullCompoundCommand`.
38#[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
55/// Executes the function definition command.
56///
57/// First, the function name is [expanded](expand_word). If the expansion fails,
58/// the execution ends with a non-zero exit status. Next, the environment is
59/// examined for an existing function having the same name.  If there is such a
60/// function that is read-only, the execution ends with a non-zero exit status.
61/// Finally, the function definition is inserted into the environment, and the
62/// execution ends with an exit status of zero.
63///
64/// The `ErrExit` shell option is [applied](Env::apply_errexit) on error.
65impl<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    // Expand the function name
77    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    // Prepare the function instance
86    let body: Rc<syntax::FullCompoundCommand> = Rc::clone(&def.body);
87    let body = Rc::into_raw(body).cast::<BodyImpl>();
88    // Safety: `BodyImpl` is `#[repr(transparent)]` over `FullCompoundCommand`,
89    // so it's safe to transmute the content of the `Rc`.
90    let body = unsafe { Rc::from_raw(body) };
91    let function = Function::new(name, body as Rc<dyn FunctionBodyObject<S>>, origin);
92
93    // Define the function
94    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
106/// Reports a function definition error.
107///
108/// This function assumes `error.existing.read_only_location.is_some()`.
109async 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        // Body equality with original AST cannot be compared directly due to BodyImpl wrapping.
176        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        // Body equality with original AST cannot be compared directly due to BodyImpl wrapping.
204        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}