Fix parse-time pipeline type checking to support multiple output types for same input type (#16111)

# Description
Fixes #15485

This PR changes pipeline checking to keep track of all possible output
types instead of only first type matching input type which appears in
the input/output types. For example, in this command:
```nushell
def foo []: [int -> string, int -> record] {
  # ...
}
```
An `int` input to the command may result in a string or a record to be
output. Before this PR, Nushell would always assume that an `int` input
would cause a `string` output because it's the first matching
input/output type pair. This would cause issues during type checking
where the parser would incorrectly determine the output type. After this
PR, Nushell considers the command to output either a string or a record.

# User-Facing Changes
* Parse-time pipeline type checking now properly supports commands with
multiple pipeline output types for the same pipeline input type

# Tests + Formatting
Added a couple tests

# After Submitting
N/A

---------

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
This commit is contained in:
132ikl
2025-08-01 21:35:25 -04:00
committed by GitHub
parent eb8d2d3206
commit da9615f971
7 changed files with 164 additions and 83 deletions

View File

@ -6,6 +6,7 @@ use nu_protocol::{
PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals,
Signature, Span, Spanned, Type, Value, VarId,
ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
combined_type_string,
debugger::DebugContext,
engine::{
Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet,
@ -1315,37 +1316,21 @@ fn check_input_types(
return Ok(());
}
let mut input_types = io_types
.iter()
.map(|(input, _)| input.to_string())
.collect::<Vec<String>>();
let input_types: Vec<Type> = io_types.iter().map(|(input, _)| input.clone()).collect();
let expected_string = combined_type_string(&input_types, "and");
let expected_string = match input_types.len() {
0 => {
return Err(ShellError::NushellFailed {
msg: "Command input type strings is empty, despite being non-zero earlier"
.to_string(),
});
}
1 => input_types.swap_remove(0),
2 => input_types.join(" and "),
_ => {
input_types
.last_mut()
.expect("Vector with length >2 has no elements")
.insert_str(0, "and ");
input_types.join(", ")
}
};
match input {
PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: head }),
_ => Err(ShellError::OnlySupportsThisInputType {
match (input, expected_string) {
(PipelineData::Empty, _) => Err(ShellError::PipelineEmpty { dst_span: head }),
(_, Some(expected_string)) => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: expected_string,
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.span().unwrap_or(Span::unknown()),
}),
// expected_string didn't generate properly, so we can't show the proper error
(_, None) => Err(ShellError::NushellFailed {
msg: "Command input type strings is empty, despite being non-zero earlier".to_string(),
}),
}
}