mirror of
https://github.com/nushell/nushell.git
synced 2024-11-24 17:34:00 +01:00
do
command: Make closure support default parameters and type checking (#12056)
# Description
Fixes: #11287
Fixes: #11318
It's implemented by porting the similar logic in `eval_call`, I've tried
to reduce duplicate code, but it seems that it's hard without using
macros.
3ee2fc60f9/crates/nu-engine/src/eval.rs (L60-L130)
It only works for `do` command.
# User-Facing Changes
## Closure supports optional parameter
```nushell
let code = {|x?| print ($x | default "i'm the default")}
do $code
```
Previously it raises an error, after this change, it prints `i'm the
default`.
## Closure supports type checking
```nushell
let code = {|x: int| echo $x}
do $code "aa"
```
After this change, it will raise an error with a message: `can't convert
string to int`
# Tests + Formatting
Done
# After Submitting
NaN
This commit is contained in:
parent
27edef4874
commit
5596190377
@ -6,7 +6,7 @@ use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature,
|
||||
SyntaxShape, Type, Value,
|
||||
Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -82,44 +82,8 @@ impl Command for Do {
|
||||
let mut callee_stack = caller_stack.captures_to_stack(block.captures);
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
|
||||
let params: Vec<_> = block
|
||||
.signature
|
||||
.required_positional
|
||||
.iter()
|
||||
.chain(block.signature.optional_positional.iter())
|
||||
.collect();
|
||||
|
||||
for param in params.iter().zip(&rest) {
|
||||
if let Some(var_id) = param.0.var_id {
|
||||
callee_stack.add_var(var_id, param.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(param) = &block.signature.rest_positional {
|
||||
if rest.len() > params.len() {
|
||||
let mut rest_items = vec![];
|
||||
|
||||
for r in rest.into_iter().skip(params.len()) {
|
||||
rest_items.push(r);
|
||||
}
|
||||
|
||||
let span = if let Some(rest_item) = rest_items.first() {
|
||||
rest_item.span()
|
||||
} else {
|
||||
call.head
|
||||
};
|
||||
|
||||
callee_stack.add_var(
|
||||
param
|
||||
.var_id
|
||||
.expect("Internal error: rest positional parameter lacks var_id"),
|
||||
Value::list(rest_items, span),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
bind_args_to(&mut callee_stack, &block.signature, rest, call.head)?;
|
||||
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
|
||||
|
||||
let result = eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut callee_stack,
|
||||
@ -324,6 +288,79 @@ impl Command for Do {
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_args_to(
|
||||
stack: &mut Stack,
|
||||
signature: &Signature,
|
||||
args: Vec<Value>,
|
||||
head_span: Span,
|
||||
) -> Result<(), ShellError> {
|
||||
let mut val_iter = args.into_iter();
|
||||
for (param, required) in signature
|
||||
.required_positional
|
||||
.iter()
|
||||
.map(|p| (p, true))
|
||||
.chain(signature.optional_positional.iter().map(|p| (p, false)))
|
||||
{
|
||||
let var_id = param
|
||||
.var_id
|
||||
.expect("internal error: all custom parameters must have var_ids");
|
||||
if let Some(result) = val_iter.next() {
|
||||
let param_type = param.shape.to_type();
|
||||
if required && !result.get_type().is_subtype(¶m_type) {
|
||||
// need to check if result is an empty list, and param_type is table or list
|
||||
// nushell needs to pass type checking for the case.
|
||||
let empty_list_matches = result
|
||||
.as_list()
|
||||
.map(|l| l.is_empty() && matches!(param_type, Type::List(_) | Type::Table(_)))
|
||||
.unwrap_or(false);
|
||||
|
||||
if !empty_list_matches {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: param.shape.to_type().to_string(),
|
||||
from_type: result.get_type().to_string(),
|
||||
span: result.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
stack.add_var(var_id, result);
|
||||
} else if let Some(value) = ¶m.default_value {
|
||||
stack.add_var(var_id, value.to_owned())
|
||||
} else if !required {
|
||||
stack.add_var(var_id, Value::nothing(head_span))
|
||||
} else {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: param.name.to_string(),
|
||||
span: head_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &signature.rest_positional {
|
||||
let mut rest_items = vec![];
|
||||
|
||||
for result in
|
||||
val_iter.skip(signature.required_positional.len() + signature.optional_positional.len())
|
||||
{
|
||||
rest_items.push(result);
|
||||
}
|
||||
|
||||
let span = if let Some(rest_item) = rest_items.first() {
|
||||
rest_item.span()
|
||||
} else {
|
||||
head_span
|
||||
};
|
||||
|
||||
stack.add_var(
|
||||
rest_positional
|
||||
.var_id
|
||||
.expect("Internal error: rest positional parameter lacks var_id"),
|
||||
Value::list(rest_items, span),
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
|
@ -521,6 +521,26 @@ fn run_dynamic_closures() {
|
||||
assert_eq!(actual.out, "holaaaa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_closure_type_check() {
|
||||
let actual = nu!(r#"let closure = {|x: int| echo $x}; do $closure "aa""#);
|
||||
assert!(actual.err.contains("can't convert string to int"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_closure_optional_arg() {
|
||||
let actual = nu!(r#"let closure = {|x: int = 3| echo $x}; do $closure"#);
|
||||
assert_eq!(actual.out, "3");
|
||||
let actual = nu!(r#"let closure = {|x: int = 3| echo $x}; do $closure 10"#);
|
||||
assert_eq!(actual.out, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_closure_rest_args() {
|
||||
let actual = nu!(r#"let closure = {|...args| $args | str join ""}; do $closure 1 2 3"#);
|
||||
assert_eq!(actual.out, "123");
|
||||
}
|
||||
|
||||
#[cfg(feature = "which-support")]
|
||||
#[test]
|
||||
fn argument_subexpression_reports_errors() {
|
||||
|
Loading…
Reference in New Issue
Block a user