Skip to main content

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