Skip to main content

nu_engine/
call_ext.rs

1use crate::eval_expression;
2use crate::named_flags::flag_type_accepts_nothing;
3use nu_protocol::{
4    FromValue, ShellError, Span, Value, ast,
5    debugger::WithoutDebug,
6    engine::{self, EngineState, Stack, StateWorkingSet},
7    eval_const::eval_constant,
8    ir,
9};
10
11pub trait CallExt {
12    /// Check if a boolean flag is set (i.e. `--bool` or `--bool=true`)
13    fn has_flag(
14        &self,
15        engine_state: &EngineState,
16        stack: &mut Stack,
17        flag_name: &str,
18    ) -> Result<bool, ShellError>;
19
20    fn get_flag<T: FromValue>(
21        &self,
22        engine_state: &EngineState,
23        stack: &mut Stack,
24        name: &str,
25    ) -> Result<Option<T>, ShellError>;
26
27    /// Efficiently get the span of a flag argument
28    fn get_flag_span(&self, stack: &Stack, name: &str) -> Option<Span>;
29
30    fn rest<T: FromValue>(
31        &self,
32        engine_state: &EngineState,
33        stack: &mut Stack,
34        starting_pos: usize,
35    ) -> Result<Vec<T>, ShellError>;
36
37    /// Returns the rest arguments starting at `starting_pos`, preserving whether each item was
38    /// passed as a spread argument.
39    ///
40    /// The tuple contains `(value, is_spread)`. This differs from [`CallExt::rest()`], which
41    /// flattens spread arguments into a single list of values.
42    fn rest_preserving_spreads<T: FromValue>(
43        &self,
44        engine_state: &EngineState,
45        stack: &mut Stack,
46        starting_pos: usize,
47    ) -> Result<Vec<(T, bool)>, ShellError>;
48
49    fn opt<T: FromValue>(
50        &self,
51        engine_state: &EngineState,
52        stack: &mut Stack,
53        pos: usize,
54    ) -> Result<Option<T>, ShellError>;
55
56    fn opt_const<T: FromValue>(
57        &self,
58        working_set: &StateWorkingSet,
59        stack: &Stack,
60        pos: usize,
61    ) -> Result<Option<T>, ShellError>;
62
63    fn req<T: FromValue>(
64        &self,
65        engine_state: &EngineState,
66        stack: &mut Stack,
67        pos: usize,
68    ) -> Result<T, ShellError>;
69
70    fn req_parser_info<T: FromValue>(
71        &self,
72        engine_state: &EngineState,
73        stack: &mut Stack,
74        name: &str,
75    ) -> Result<T, ShellError>;
76
77    /// True if the command has any positional or rest arguments, excluding before the given index.
78    fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool;
79}
80
81impl CallExt for ast::Call {
82    fn has_flag(
83        &self,
84        engine_state: &EngineState,
85        stack: &mut Stack,
86        flag_name: &str,
87    ) -> Result<bool, ShellError> {
88        for name in self.named_iter() {
89            if flag_name == name.0.item {
90                return if let Some(expr) = &name.2 {
91                    // Check --flag=false
92                    let stack = &mut stack.use_call_arg_out_dest();
93                    let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
94                    match result {
95                        Value::Bool { val, .. } => Ok(val),
96                        _ => Err(ShellError::CantConvert {
97                            to_type: "bool".into(),
98                            from_type: result.get_type().to_string(),
99                            span: result.span(),
100                            help: Some("".into()),
101                        }),
102                    }
103                } else {
104                    Ok(true)
105                };
106            }
107        }
108
109        Ok(false)
110    }
111
112    fn get_flag<T: FromValue>(
113        &self,
114        engine_state: &EngineState,
115        stack: &mut Stack,
116        name: &str,
117    ) -> Result<Option<T>, ShellError> {
118        if let Some(expr) = self.get_flag_expr(name) {
119            let stack = &mut stack.use_call_arg_out_dest();
120            let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
121            // Signature-aware null: omit when the flag type does not accept nothing
122            // (IR path does this in normalize_call_arguments before get_flag runs).
123            if result.is_nothing() {
124                let accepts = engine_state
125                    .get_decl(self.decl_id)
126                    .signature()
127                    .get_long_flag(name)
128                    .is_some_and(|flag| flag_type_accepts_nothing(&flag));
129                if !accepts {
130                    return Ok(None);
131                }
132            }
133            FromValue::from_value(result).map(Some)
134        } else {
135            Ok(None)
136        }
137    }
138
139    fn get_flag_span(&self, _stack: &Stack, name: &str) -> Option<Span> {
140        self.get_named_arg(name).map(|arg| arg.span)
141    }
142
143    fn rest<T: FromValue>(
144        &self,
145        engine_state: &EngineState,
146        stack: &mut Stack,
147        starting_pos: usize,
148    ) -> Result<Vec<T>, ShellError> {
149        let stack = &mut stack.use_call_arg_out_dest();
150        self.rest_iter_flattened(starting_pos, |expr| {
151            eval_expression::<WithoutDebug>(engine_state, stack, expr)
152        })?
153        .into_iter()
154        .map(FromValue::from_value)
155        .collect()
156    }
157
158    fn rest_preserving_spreads<T: FromValue>(
159        &self,
160        engine_state: &EngineState,
161        stack: &mut Stack,
162        starting_pos: usize,
163    ) -> Result<Vec<(T, bool)>, ShellError> {
164        let stack = &mut stack.use_call_arg_out_dest();
165        self.rest_iter(starting_pos)
166            .map(|(expr, is_spread)| {
167                let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
168                Ok((T::from_value(result)?, is_spread))
169            })
170            .collect()
171    }
172
173    fn opt<T: FromValue>(
174        &self,
175        engine_state: &EngineState,
176        stack: &mut Stack,
177        pos: usize,
178    ) -> Result<Option<T>, ShellError> {
179        if let Some(expr) = self.positional_iter().nth(pos) {
180            let stack = &mut stack.use_call_arg_out_dest();
181            let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
182            if result.is_nothing() {
183                Ok(None)
184            } else {
185                FromValue::from_value(result).map(Some)
186            }
187        } else {
188            Ok(None)
189        }
190    }
191
192    fn opt_const<T: FromValue>(
193        &self,
194        working_set: &StateWorkingSet,
195        _stack: &Stack,
196        pos: usize,
197    ) -> Result<Option<T>, ShellError> {
198        if let Some(expr) = self.positional_iter().nth(pos) {
199            let result = eval_constant(working_set, expr)?;
200            if result.is_nothing() {
201                Ok(None)
202            } else {
203                FromValue::from_value(result).map(Some)
204            }
205        } else {
206            Ok(None)
207        }
208    }
209
210    fn req<T: FromValue>(
211        &self,
212        engine_state: &EngineState,
213        stack: &mut Stack,
214        pos: usize,
215    ) -> Result<T, ShellError> {
216        let maybe_expr = self.positional_iter().nth(pos);
217        let expr = maybe_expr.ok_or_else(|| {
218            let max_idx = self.positional_iter().count().checked_sub(1);
219            match max_idx {
220                None => ShellError::AccessEmptyContent { span: self.head },
221                Some(max_idx) => ShellError::AccessBeyondEnd {
222                    max_idx,
223                    span: self.head,
224                },
225            }
226        })?;
227
228        let stack = &mut stack.use_call_arg_out_dest();
229        let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
230        FromValue::from_value(result)
231    }
232
233    fn req_parser_info<T: FromValue>(
234        &self,
235        engine_state: &EngineState,
236        stack: &mut Stack,
237        name: &str,
238    ) -> Result<T, ShellError> {
239        if let Some(expr) = self.get_parser_info(name) {
240            let stack = &mut stack.use_call_arg_out_dest();
241            let result = eval_expression::<WithoutDebug>(engine_state, stack, expr)?;
242            FromValue::from_value(result)
243        } else if self.parser_info.is_empty() {
244            Err(ShellError::AccessEmptyContent { span: self.head })
245        } else {
246            Err(ShellError::AccessBeyondEnd {
247                max_idx: self.parser_info.len() - 1,
248                span: self.head,
249            })
250        }
251    }
252
253    fn has_positional_args(&self, _stack: &Stack, starting_pos: usize) -> bool {
254        self.rest_iter(starting_pos).next().is_some()
255    }
256}
257
258impl CallExt for ir::Call {
259    fn has_flag(
260        &self,
261        _engine_state: &EngineState,
262        stack: &mut Stack,
263        flag_name: &str,
264    ) -> Result<bool, ShellError> {
265        Ok(self
266            .named_iter(stack)
267            .find(|(name, _)| name.item == flag_name)
268            .is_some_and(|(_, value)| {
269                // Handle --flag=false
270                !matches!(value, Some(Value::Bool { val: false, .. }))
271            }))
272    }
273
274    fn get_flag<T: FromValue>(
275        &self,
276        _engine_state: &EngineState,
277        stack: &mut Stack,
278        name: &str,
279    ) -> Result<Option<T>, ShellError> {
280        // Null flags that are not type-accepted are dropped in normalize_call_arguments.
281        // Remaining null values are intentional (flag type accepts nothing).
282        if let Some(val) = self.get_named_arg(stack, name) {
283            T::from_value(val.clone()).map(Some)
284        } else {
285            Ok(None)
286        }
287    }
288
289    fn get_flag_span(&self, stack: &Stack, name: &str) -> Option<Span> {
290        self.named_iter(stack)
291            .find_map(|(i_name, _)| (i_name.item == name).then_some(i_name.span))
292    }
293
294    fn rest<T: FromValue>(
295        &self,
296        _engine_state: &EngineState,
297        stack: &mut Stack,
298        starting_pos: usize,
299    ) -> Result<Vec<T>, ShellError> {
300        self.rest_iter_flattened(stack, starting_pos)?
301            .into_iter()
302            .map(T::from_value)
303            .collect()
304    }
305
306    fn rest_preserving_spreads<T: FromValue>(
307        &self,
308        _engine_state: &EngineState,
309        stack: &mut Stack,
310        starting_pos: usize,
311    ) -> Result<Vec<(T, bool)>, ShellError> {
312        self.rest_iter(stack, starting_pos)
313            .map(|(val, is_spread)| Ok((T::from_value(val.clone())?, is_spread)))
314            .collect()
315    }
316
317    fn opt<T: FromValue>(
318        &self,
319        _engine_state: &EngineState,
320        stack: &mut Stack,
321        pos: usize,
322    ) -> Result<Option<T>, ShellError> {
323        self.positional_iter(stack)
324            .nth(pos)
325            .filter(|v| !v.is_nothing())
326            .cloned()
327            .map(T::from_value)
328            .transpose()
329    }
330
331    fn opt_const<T: FromValue>(
332        &self,
333        _working_set: &StateWorkingSet,
334        stack: &Stack,
335        pos: usize,
336    ) -> Result<Option<T>, ShellError> {
337        self.positional_iter(stack)
338            .nth(pos)
339            .filter(|v| !v.is_nothing())
340            .cloned()
341            .map(T::from_value)
342            .transpose()
343    }
344
345    fn req<T: FromValue>(
346        &self,
347        _engine_state: &EngineState,
348        stack: &mut Stack,
349        pos: usize,
350    ) -> Result<T, ShellError> {
351        let maybe_val = self.positional_iter(stack).nth(pos).cloned();
352        let val = maybe_val.ok_or_else(|| {
353            let max_idx = self.positional_iter(stack).count().checked_sub(1);
354            match max_idx {
355                None => ShellError::AccessEmptyContent { span: self.head },
356                Some(max_idx) => ShellError::AccessBeyondEnd {
357                    max_idx,
358                    span: self.head,
359                },
360            }
361        })?;
362
363        T::from_value(val)
364    }
365
366    fn req_parser_info<T: FromValue>(
367        &self,
368        engine_state: &EngineState,
369        stack: &mut Stack,
370        name: &str,
371    ) -> Result<T, ShellError> {
372        // FIXME: this depends on the AST evaluator. We can fix this by making the parser info an
373        // enum rather than using expressions. It's not clear that evaluation of this is ever really
374        // needed.
375        if let Some(expr) = self.get_parser_info(stack, name) {
376            let expr = expr.clone();
377            let stack = &mut stack.use_call_arg_out_dest();
378            let result = eval_expression::<WithoutDebug>(engine_state, stack, &expr)?;
379            FromValue::from_value(result)
380        } else {
381            Err(ShellError::CantFindColumn {
382                col_name: name.into(),
383                span: None,
384                src_span: self.head,
385            })
386        }
387    }
388
389    fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool {
390        self.rest_iter(stack, starting_pos).next().is_some()
391    }
392}
393
394macro_rules! proxy {
395    ($self:ident . $method:ident ($($param:expr),*)) => (match &$self.inner {
396        engine::CallImpl::AstRef(call) => call.$method($($param),*),
397        engine::CallImpl::AstBox(call) => call.$method($($param),*),
398        engine::CallImpl::IrRef(call) => call.$method($($param),*),
399        engine::CallImpl::IrBox(call) => call.$method($($param),*),
400    })
401}
402
403impl CallExt for engine::Call<'_> {
404    fn has_flag(
405        &self,
406        engine_state: &EngineState,
407        stack: &mut Stack,
408        flag_name: &str,
409    ) -> Result<bool, ShellError> {
410        proxy!(self.has_flag(engine_state, stack, flag_name))
411    }
412
413    fn get_flag<T: FromValue>(
414        &self,
415        engine_state: &EngineState,
416        stack: &mut Stack,
417        name: &str,
418    ) -> Result<Option<T>, ShellError> {
419        proxy!(self.get_flag(engine_state, stack, name))
420    }
421
422    fn get_flag_span(&self, stack: &Stack, name: &str) -> Option<Span> {
423        proxy!(self.get_flag_span(stack, name))
424    }
425
426    fn rest<T: FromValue>(
427        &self,
428        engine_state: &EngineState,
429        stack: &mut Stack,
430        starting_pos: usize,
431    ) -> Result<Vec<T>, ShellError> {
432        proxy!(self.rest(engine_state, stack, starting_pos))
433    }
434
435    fn rest_preserving_spreads<T: FromValue>(
436        &self,
437        engine_state: &EngineState,
438        stack: &mut Stack,
439        starting_pos: usize,
440    ) -> Result<Vec<(T, bool)>, ShellError> {
441        proxy!(self.rest_preserving_spreads(engine_state, stack, starting_pos))
442    }
443
444    fn opt<T: FromValue>(
445        &self,
446        engine_state: &EngineState,
447        stack: &mut Stack,
448        pos: usize,
449    ) -> Result<Option<T>, ShellError> {
450        proxy!(self.opt(engine_state, stack, pos))
451    }
452
453    fn opt_const<T: FromValue>(
454        &self,
455        working_set: &StateWorkingSet,
456        stack: &Stack,
457        pos: usize,
458    ) -> Result<Option<T>, ShellError> {
459        proxy!(self.opt_const(working_set, stack, pos))
460    }
461
462    fn req<T: FromValue>(
463        &self,
464        engine_state: &EngineState,
465        stack: &mut Stack,
466        pos: usize,
467    ) -> Result<T, ShellError> {
468        proxy!(self.req(engine_state, stack, pos))
469    }
470
471    fn req_parser_info<T: FromValue>(
472        &self,
473        engine_state: &EngineState,
474        stack: &mut Stack,
475        name: &str,
476    ) -> Result<T, ShellError> {
477        proxy!(self.req_parser_info(engine_state, stack, name))
478    }
479
480    fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool {
481        proxy!(self.has_positional_args(stack, starting_pos))
482    }
483}