Skip to content

do command: Make closure support default parameters and type checking#12056

Merged
WindSoilder merged 6 commits intonushell:mainfrom
WindSoilder:better_closure_with_do
Mar 11, 2024
Merged

do command: Make closure support default parameters and type checking#12056
WindSoilder merged 6 commits intonushell:mainfrom
WindSoilder:better_closure_with_do

Conversation

@WindSoilder
Copy link
Copy Markdown
Contributor

@WindSoilder WindSoilder commented Mar 3, 2024

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.

for (param_idx, (param, required)) in decl
.signature()
.required_positional
.iter()
.map(|p| (p, true))
.chain(
decl.signature()
.optional_positional
.iter()
.map(|p| (p, false)),
)
.enumerate()
{
let var_id = param
.var_id
.expect("internal error: all custom parameters must have var_ids");
if let Some(arg) = call.positional_nth(param_idx) {
let result = eval_expression(engine_state, caller_stack, arg)?;
let param_type = param.shape.to_type();
if required && !result.get_type().is_subtype(&param_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,
});
}
}
callee_stack.add_var(var_id, result);
} else if let Some(value) = &param.default_value {
callee_stack.add_var(var_id, value.to_owned());
} else {
callee_stack.add_var(var_id, Value::nothing(call.head));
}
}
if let Some(rest_positional) = decl.signature().rest_positional {
let mut rest_items = vec![];
for result in call.rest_iter_flattened(
decl.signature().required_positional.len()
+ decl.signature().optional_positional.len(),
|expr| eval_expression(engine_state, caller_stack, expr),
)? {
rest_items.push(result);
}
let span = if let Some(rest_item) = rest_items.first() {
rest_item.span()
} else {
call.head
};
callee_stack.add_var(
rest_positional
.var_id
.expect("Internal error: rest positional parameter lacks var_id"),
Value::list(rest_items, span),
)
}

It only works for do command.

User-Facing Changes

Closure supports optional parameter

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

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

@WindSoilder WindSoilder added notes:fixes Include the release notes summary in the "Bug fixes" section deprecated:pr-language (deprecated: this label is too vague) This PR makes some language changes labels Mar 6, 2024
@sholderbach
Copy link
Copy Markdown
Member

While I can kind of follow what you are doing for do, how would all this play out for our other commands that accept a closure?

Can we perform type checking?

And do we have any remaining commands that sniff for the number of parameters of a closure to decide what they do? Here default values may complicate the semantics.

@WindSoilder
Copy link
Copy Markdown
Contributor Author

how would all this play out for our other commands that accept a closure?

I don't think we need to handle for them, because many other commands like each, skip while auto-accept a parameter from previous command. Currently user can only use do to pass argument to closure manually.

Can we perform type checking?

I'm not really sure if it's worth to add type checking for something like each and skip while closure. And I guess it can be a big change if we want ot support it, we might need to restructure the codebase around closure.

@sholderbach
Copy link
Copy Markdown
Member

I am OK with going step by step and landing this improvement for do. Then I would just suggest adding to the PR title that this is for do and go ahead with landing.

@WindSoilder WindSoilder changed the title Make closure support default parameters and type checking do command: Make closure support default parameters and type checking Mar 11, 2024
@WindSoilder
Copy link
Copy Markdown
Contributor Author

Sure! I have changed the title and description

@WindSoilder WindSoilder merged commit 5596190 into nushell:main Mar 11, 2024
@hustcer hustcer added this to the v0.92.0 milestone Mar 11, 2024
@NotTheDr01ds
Copy link
Copy Markdown
Contributor

Currently user can only use do to pass argument to closure manually

and generate - See #12084

@NotTheDr01ds
Copy link
Copy Markdown
Contributor

NotTheDr01ds commented Apr 21, 2024

Also, FWIW, reduce, but in that case the default argument is, of course, passed in with --fold.

However, it's probably worth considering allowing reduce to also accept a default parameter for acc. (Useless) Simple example:

[ 1 2 3 4 5 ] | reduce  {|it,acc=10| $acc + $it }

This would be expected to return 25, but since the default acc=10 is ignored, the result is 15.

As far as I can tell, reduce --fold/-f seems like it could be replaced with default arguments.

IanManske pushed a commit that referenced this pull request May 17, 2024
# Description
Fixes: #12690 

The issue is happened after
#12056 is merged. It will raise
error if user doesn't supply required parameter when run closure with
do.
And parser adds a `$it` parameter when parsing closure or block
expression.

I believe the previous behavior is because we allow such syntax on
previous version(0.44):
```nushell
let x = { print $it }
```
But it's no longer allowed after 0.60.  So I think they can be removed.

# User-Facing Changes
```nushell
let tmp = {
  let it = 42
  print $it
}

do -c $tmp
```
should be possible again.

# Tests + Formatting
Added 1 test
FilipAndersson245 pushed a commit to FilipAndersson245/nushell that referenced this pull request May 18, 2024
# Description
Fixes: nushell#12690 

The issue is happened after
nushell#12056 is merged. It will raise
error if user doesn't supply required parameter when run closure with
do.
And parser adds a `$it` parameter when parsing closure or block
expression.

I believe the previous behavior is because we allow such syntax on
previous version(0.44):
```nushell
let x = { print $it }
```
But it's no longer allowed after 0.60.  So I think they can be removed.

# User-Facing Changes
```nushell
let tmp = {
  let it = 42
  print $it
}

do -c $tmp
```
should be possible again.

# Tests + Formatting
Added 1 test
@WindSoilder WindSoilder deleted the better_closure_with_do branch May 23, 2024 02:57
@NotTheDr01ds NotTheDr01ds mentioned this pull request Jun 20, 2024
fdncred pushed a commit that referenced this pull request Jun 20, 2024
# Description

#12056 added support for default and type-checked arguments in `do`
closures.

This PR adds examples for those features.  It also:

* Fixes the TODO (a closure parameter that wasn't being used) that was
preventing a result from being added
* Removes extraneous commas from the descriptions
* Adds an example demonstrating multiple positional closure arguments

# User-Facing Changes

Help examples only

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deprecated:pr-language (deprecated: this label is too vague) This PR makes some language changes notes:fixes Include the release notes summary in the "Bug fixes" section

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow default values for closure parameters closures and commands don't treat optional parameters the same

4 participants