mirror of
https://github.com/nushell/nushell.git
synced 2024-11-28 11:24:09 +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::engine::{Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature,
|
Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature,
|
||||||
SyntaxShape, Type, Value,
|
Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -82,44 +82,8 @@ impl Command for Do {
|
|||||||
let mut callee_stack = caller_stack.captures_to_stack(block.captures);
|
let mut callee_stack = caller_stack.captures_to_stack(block.captures);
|
||||||
let block = engine_state.get_block(block.block_id);
|
let block = engine_state.get_block(block.block_id);
|
||||||
|
|
||||||
let params: Vec<_> = block
|
bind_args_to(&mut callee_stack, &block.signature, rest, call.head)?;
|
||||||
.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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
|
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
|
||||||
|
|
||||||
let result = eval_block_with_early_return(
|
let result = eval_block_with_early_return(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut callee_stack,
|
&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 {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
|
@ -521,6 +521,26 @@ fn run_dynamic_closures() {
|
|||||||
assert_eq!(actual.out, "holaaaa");
|
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")]
|
#[cfg(feature = "which-support")]
|
||||||
#[test]
|
#[test]
|
||||||
fn argument_subexpression_reports_errors() {
|
fn argument_subexpression_reports_errors() {
|
||||||
|
Loading…
Reference in New Issue
Block a user