Improve type hovers (#9515)

# Description

This PR does a few things to help improve type hovers and, in the
process, fixes a few outstanding issues in the type system. Here's a
list of the changes:

* `for` now will try to infer the type of the iteration variable based
on the expression it's given. This fixes things like `for x in [1, 2, 3]
{ }` where `x` now properly gets the int type.
* Removed old input/output type fields from the signature, focuses on
the vec of signatures. Updated a bunch of dataframe commands that hadn't
moved over. This helps tie things together a bit better
* Fixed inference of types from subexpressions to use the last
expression in the block
* Fixed handling of explicit types in `let` and `mut` calls, so we now
respect that as the authoritative type

I also tried to add `def` input/output type inference, but unfortunately
we only know the predecl types universally, which means we won't have
enough information to properly know what the types of the custom
commands are.

# User-Facing Changes

Script typechecking will get tighter in some cases
Hovers should be more accurate in some cases that previously resorted to
any.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# 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.
-->

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
JT
2023-06-29 05:19:48 +12:00
committed by GitHub
parent 9d247387ea
commit 9068093081
112 changed files with 681 additions and 334 deletions

View File

@ -1,4 +1,4 @@
use crate::parser_path::ParserPath;
use crate::{parser_path::ParserPath, type_check::type_compatible};
use itertools::Itertools;
use log::trace;
use nu_path::canonicalize_with;
@ -235,7 +235,7 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(b"for", &Type::Any) {
let (call, call_span) = match working_set.find_decl(b"for", &Type::Nothing) {
None => {
working_set.error(ParseError::UnknownState(
"internal error: for declaration not found".into(),
@ -290,9 +290,21 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
// All positional arguments must be in the call positional vector by this point
let var_decl = call.positional_nth(0).expect("for call already checked");
let iteration_expr = call.positional_nth(1).expect("for call already checked");
let block = call.positional_nth(2).expect("for call already checked");
let iteration_expr_ty = iteration_expr.ty.clone();
// Figure out the type of the variable the `for` uses for iteration
let var_type = match iteration_expr_ty {
Type::List(x) => *x,
Type::Table(x) => Type::Record(x),
x => x,
};
if let (Some(var_id), Some(block_id)) = (&var_decl.as_var(), block.as_block()) {
working_set.set_variable_type(*var_id, var_type.clone());
let block = working_set.get_block_mut(block_id);
block.signature.required_positional.insert(
@ -300,7 +312,7 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
PositionalArg {
name: String::new(),
desc: String::new(),
shape: SyntaxShape::Any,
shape: var_type.to_shape(),
var_id: Some(*var_id),
default_value: None,
},
@ -310,7 +322,7 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
ty: Type::Nothing,
custom_completion: None,
}
}
@ -347,7 +359,7 @@ pub fn parse_def(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(&def_call, &Type::Any) {
let (call, call_span) = match working_set.find_decl(&def_call, &Type::Nothing) {
None => {
working_set.error(ParseError::UnknownState(
"internal error: def declaration not found".into(),
@ -391,12 +403,15 @@ pub fn parse_def(
expr: Expr::Block(block_id),
..
}
| Expression {
expr: Expr::Closure(block_id),
..
}
| Expression {
expr: Expr::RowCondition(block_id),
..
} => {
let block = working_set.get_block_mut(*block_id);
block.signature = Box::new(sig.clone());
}
_ => {}
@ -469,6 +484,20 @@ pub fn parse_def(
block.recursive = Some(calls_itself);
block.signature = signature;
block.redirect_env = def_call == b"def-env";
// Sadly we can't use this here as the inference would have to happen before
// all the definitions had been fully parsed.
// infer the return type based on the output of the block
// let block = working_set.get_block(block_id);
// let input_type = block.input_type(working_set);
// let output_type = block.output_type();
// block.signature.input_output_types = vec![(input_type, output_type)];
block
.signature
.input_output_types
.push((Type::Any, Type::Any));
} else {
working_set.error(ParseError::InternalError(
"Predeclaration failed to add declaration".into(),
@ -519,7 +548,7 @@ pub fn parse_extern(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(&extern_call, &Type::Any) {
let (call, call_span) = match working_set.find_decl(&extern_call, &Type::Nothing) {
None => {
working_set.error(ParseError::UnknownState(
"internal error: def declaration not found".into(),
@ -710,7 +739,7 @@ pub fn parse_alias(
return Pipeline::from_vec(vec![garbage(*span)]);
}
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Nothing) {
let (command_spans, rest_spans) = spans.split_at(split_id);
let original_starting_error_count = working_set.parse_errors.len();
@ -912,7 +941,7 @@ pub fn parse_export_in_block(
b"export".to_vec()
};
if let Some(decl_id) = working_set.find_decl(&full_name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(&full_name, &Type::Nothing) {
let ParsedInternalCall { call, output, .. } = parse_internal_call(
working_set,
if full_name == b"export" {
@ -1006,7 +1035,7 @@ pub fn parse_export_in_module(
return (garbage_pipeline(spans), vec![]);
};
let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Any) {
let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1036,7 +1065,7 @@ pub fn parse_export_in_module(
let pipeline = parse_def(working_set, &lite_command, Some(module_name));
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export def", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export def", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1072,7 +1101,7 @@ pub fn parse_export_in_module(
let decl_name = working_set.get_span_contents(*decl_name_span);
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Nothing) {
result.push(Exportable::Decl {
name: decl_name.to_vec(),
id: decl_id,
@ -1095,7 +1124,7 @@ pub fn parse_export_in_module(
let pipeline = parse_def(working_set, &lite_command, Some(module_name));
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export def-env", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export def-env", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1133,7 +1162,7 @@ pub fn parse_export_in_module(
};
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Nothing) {
result.push(Exportable::Decl {
name: decl_name.to_vec(),
id: decl_id,
@ -1155,7 +1184,7 @@ pub fn parse_export_in_module(
let pipeline = parse_extern(working_set, &lite_command, Some(module_name));
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export extern", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export extern", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1193,7 +1222,7 @@ pub fn parse_export_in_module(
};
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Nothing) {
result.push(Exportable::Decl {
name: decl_name.to_vec(),
id: decl_id,
@ -1215,7 +1244,7 @@ pub fn parse_export_in_module(
let pipeline = parse_alias(working_set, &lite_command, Some(module_name));
let export_alias_decl_id =
if let Some(id) = working_set.find_decl(b"export alias", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export alias", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1253,7 +1282,7 @@ pub fn parse_export_in_module(
};
let alias_name = trim_quotes(alias_name);
if let Some(alias_id) = working_set.find_decl(alias_name, &Type::Any) {
if let Some(alias_id) = working_set.find_decl(alias_name, &Type::Nothing) {
result.push(Exportable::Decl {
name: alias_name.to_vec(),
id: alias_id,
@ -1275,7 +1304,7 @@ pub fn parse_export_in_module(
let (pipeline, exportables) = parse_use(working_set, &lite_command.parts);
let export_use_decl_id =
if let Some(id) = working_set.find_decl(b"export use", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export use", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1312,7 +1341,7 @@ pub fn parse_export_in_module(
parse_module(working_set, lite_command, Some(module_name));
let export_module_decl_id =
if let Some(id) = working_set.find_decl(b"export module", &Type::Any) {
if let Some(id) = working_set.find_decl(b"export module", &Type::Nothing) {
id
} else {
working_set.error(ParseError::InternalError(
@ -1417,7 +1446,7 @@ pub fn parse_export_env(
return (garbage_pipeline(spans), None);
}
let call = match working_set.find_decl(b"export-env", &Type::Any) {
let call = match working_set.find_decl(b"export-env", &Type::Nothing) {
Some(decl_id) => {
let ParsedInternalCall { call, output } =
parse_internal_call(working_set, spans[0], &[spans[1]], decl_id);
@ -1929,7 +1958,7 @@ pub fn parse_module(
1
};
let (call, call_span) = match working_set.find_decl(b"module", &Type::Any) {
let (call, call_span) = match working_set.find_decl(b"module", &Type::Nothing) {
Some(decl_id) => {
let (command_spans, rest_spans) = spans.split_at(split_id);
@ -2070,7 +2099,7 @@ pub fn parse_module(
};
let module_decl_id = working_set
.find_decl(b"module", &Type::Any)
.find_decl(b"module", &Type::Nothing)
.expect("internal error: missing module command");
let call = Box::new(Call {
@ -2121,7 +2150,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
return (garbage_pipeline(spans), vec![]);
}
let (call, call_span, args_spans) = match working_set.find_decl(b"use", &Type::Any) {
let (call, call_span, args_spans) = match working_set.find_decl(b"use", &Type::Nothing) {
Some(decl_id) => {
let (command_spans, rest_spans) = spans.split_at(split_id);
@ -2277,7 +2306,7 @@ pub fn parse_hide(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
return garbage_pipeline(spans);
}
let (call, args_spans) = match working_set.find_decl(b"hide", &Type::Any) {
let (call, args_spans) = match working_set.find_decl(b"hide", &Type::Nothing) {
Some(decl_id) => {
let ParsedInternalCall { call, output } =
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
@ -2336,7 +2365,7 @@ pub fn parse_hide(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
(true, working_set.get_module(module_id).clone())
} else if import_pattern.members.is_empty() {
// The pattern head can be:
if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Nothing) {
// a custom command,
let mut module = Module::new(b"tmp".to_vec());
module.add_decl(import_pattern.head.name.clone(), id);
@ -2767,17 +2796,19 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
}
pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
trace!("parsing: let");
let name = working_set.get_span_contents(spans[0]);
if name == b"let" || name == b"const" {
let is_const = &name == b"const";
if let Some(span) = check_name(working_set, spans) {
return Pipeline::from_vec(vec![garbage(*span)]);
}
// JT: Disabling check_name because it doesn't work with optional types in the declaration
// if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]);
// }
if let Some(decl_id) =
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Any)
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Nothing)
{
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
@ -2805,7 +2836,8 @@ pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) ->
}
let mut idx = 0;
let lvalue = parse_var_with_opt_type(
let (lvalue, explicit_type) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
@ -2824,8 +2856,20 @@ pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) ->
let var_id = lvalue.as_var();
let rhs_type = rvalue.ty.clone();
if let Some(explicit_type) = &explicit_type {
if !type_compatible(explicit_type, &rhs_type) {
working_set.error(ParseError::TypeMismatch(
explicit_type.clone(),
rhs_type.clone(),
nu_protocol::span(&spans[(span.0 + 1)..]),
));
}
}
if let Some(var_id) = var_id {
working_set.set_variable_type(var_id, rhs_type);
if explicit_type.is_none() {
working_set.set_variable_type(var_id, rhs_type);
}
if is_const {
match eval_constant(working_set, &rvalue) {
@ -2867,6 +2911,11 @@ pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) ->
ty: output,
custom_completion: None,
}]);
} else {
working_set.error(ParseError::UnknownState(
"internal error: let or const statements not found in core language".into(),
span(spans),
))
}
}
@ -2882,11 +2931,11 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
let name = working_set.get_span_contents(spans[0]);
if name == b"mut" {
if let Some(span) = check_name(working_set, spans) {
return Pipeline::from_vec(vec![garbage(*span)]);
}
// if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]);
// }
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Any) {
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Nothing) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
@ -2913,7 +2962,7 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
}
let mut idx = 0;
let lvalue = parse_var_with_opt_type(
let (lvalue, explicit_type) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
@ -2932,8 +2981,20 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
let var_id = lvalue.as_var();
let rhs_type = rvalue.ty.clone();
if let Some(explicit_type) = &explicit_type {
if &rhs_type != explicit_type && explicit_type != &Type::Any {
working_set.error(ParseError::TypeMismatch(
explicit_type.clone(),
rhs_type.clone(),
rvalue.span,
));
}
}
if let Some(var_id) = var_id {
working_set.set_variable_type(var_id, rhs_type);
if explicit_type.is_none() {
working_set.set_variable_type(var_id, rhs_type);
}
}
let call = Box::new(Call {
@ -2982,7 +3043,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
if name == b"source" || name == b"source-env" {
let scoped = name == b"source-env";
if let Some(decl_id) = working_set.find_decl(name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(name, &Type::Nothing) {
let cwd = working_set.get_cwd();
// Is this the right call to be using here?
@ -3113,7 +3174,7 @@ pub fn parse_where_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
return garbage(span(spans));
}
let call = match working_set.find_decl(b"where", &Type::Any) {
let call = match working_set.find_decl(b"where", &Type::List(Box::new(Type::Any))) {
Some(decl_id) => {
let ParsedInternalCall { call, output } =
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
@ -3176,7 +3237,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(b"register", &Type::Any) {
let (call, call_span) = match working_set.find_decl(b"register", &Type::Nothing) {
None => {
working_set.error(ParseError::UnknownState(
"internal error: Register declaration not found".into(),

View File

@ -607,7 +607,7 @@ pub fn parse_multispan_value(
SyntaxShape::VarWithOptType => {
trace!("parsing: var with opt type");
parse_var_with_opt_type(working_set, spans, spans_idx, false)
parse_var_with_opt_type(working_set, spans, spans_idx, false).0
}
SyntaxShape::RowCondition => {
trace!("parsing: row condition");
@ -779,7 +779,7 @@ pub fn parse_internal_call(
let decl = working_set.get_decl(decl_id);
let signature = decl.signature();
let output = signature.output_type.clone();
let output = signature.get_output_type();
if decl.is_builtin() {
attach_parser_info_builtin(working_set, decl.name(), &mut call);
@ -1172,6 +1172,7 @@ pub fn parse_call(
}
pub fn parse_binary(working_set: &mut StateWorkingSet, span: Span) -> Expression {
trace!("parsing: binary");
let contents = working_set.get_span_contents(span);
if contents.starts_with(b"0x[") {
parse_binary_with_base(working_set, span, 16, 2, b"0x[", b"]")
@ -2007,25 +2008,7 @@ pub fn parse_full_cell_path(
.type_scope
.add_type(working_set.type_scope.get_last_output());
let ty = output
.pipelines
.last()
.and_then(|Pipeline { elements, .. }| elements.last())
.map(|element| match element {
PipelineElement::Expression(_, expr)
if matches!(
expr,
Expression {
expr: Expr::BinaryOp(..),
..
}
) =>
{
expr.ty.clone()
}
_ => working_set.type_scope.get_last_output(),
})
.unwrap_or_else(|| working_set.type_scope.get_last_output());
let ty = output.output_type();
let block_id = working_set.add_block(output);
tokens.next();
@ -3081,7 +3064,7 @@ pub fn parse_var_with_opt_type(
spans: &[Span],
spans_idx: &mut usize,
mutable: bool,
) -> Expression {
) -> (Expression, Option<Type>) {
let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec();
if bytes.contains(&b' ')
@ -3090,7 +3073,7 @@ pub fn parse_var_with_opt_type(
|| bytes.contains(&b'`')
{
working_set.error(ParseError::VariableNotValid(spans[*spans_idx]));
return garbage(spans[*spans_idx]);
return (garbage(spans[*spans_idx]), None);
}
if bytes.ends_with(b":") {
@ -3108,17 +3091,20 @@ pub fn parse_var_with_opt_type(
"valid variable name",
spans[*spans_idx],
));
return garbage(spans[*spans_idx]);
return (garbage(spans[*spans_idx]), None);
}
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
Expression {
expr: Expr::VarDecl(id),
span: span(&spans[*spans_idx - 1..*spans_idx + 1]),
ty,
custom_completion: None,
}
(
Expression {
expr: Expr::VarDecl(id),
span: span(&spans[*spans_idx - 1..*spans_idx + 1]),
ty: ty.clone(),
custom_completion: None,
},
Some(ty),
)
} else {
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
@ -3127,18 +3113,21 @@ pub fn parse_var_with_opt_type(
"valid variable name",
spans[*spans_idx],
));
return garbage(spans[*spans_idx]);
return (garbage(spans[*spans_idx]), None);
}
let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any, mutable);
working_set.error(ParseError::MissingType(spans[*spans_idx]));
Expression {
expr: Expr::VarDecl(id),
span: spans[*spans_idx],
ty: Type::Any,
custom_completion: None,
}
(
Expression {
expr: Expr::VarDecl(id),
span: spans[*spans_idx],
ty: Type::Any,
custom_completion: None,
},
None,
)
}
} else {
let var_name = bytes;
@ -3148,7 +3137,7 @@ pub fn parse_var_with_opt_type(
"valid variable name",
spans[*spans_idx],
));
return garbage(spans[*spans_idx]);
return (garbage(spans[*spans_idx]), None);
}
let id = working_set.add_variable(
@ -3158,12 +3147,15 @@ pub fn parse_var_with_opt_type(
mutable,
);
Expression {
expr: Expr::VarDecl(id),
span: span(&spans[*spans_idx..*spans_idx + 1]),
ty: Type::Any,
custom_completion: None,
}
(
Expression {
expr: Expr::VarDecl(id),
span: span(&spans[*spans_idx..*spans_idx + 1]),
ty: Type::Any,
custom_completion: None,
},
None,
)
}
}
@ -5088,11 +5080,13 @@ pub fn parse_builtin_commands(
lite_command: &LiteCommand,
is_subexpression: bool,
) -> Pipeline {
trace!("parsing: builtin commands");
if !is_math_expression_like(working_set, lite_command.parts[0])
&& !is_unaliasable_parser_keyword(working_set, &lite_command.parts)
{
trace!("parsing: not math expression or unaliasable parser keyword");
let name = working_set.get_span_contents(lite_command.parts[0]);
if let Some(decl_id) = working_set.find_decl(name, &Type::Any) {
if let Some(decl_id) = working_set.find_decl(name, &Type::Nothing) {
let cmd = working_set.get_decl(decl_id);
if cmd.is_alias() {
// Parse keywords that can be aliased. Note that we check for "unaliasable" keywords
@ -5122,6 +5116,7 @@ pub fn parse_builtin_commands(
}
}
trace!("parsing: checking for keywords");
let name = working_set.get_span_contents(lite_command.parts[0]);
match name {
@ -5347,9 +5342,9 @@ pub fn parse_block(
parse_builtin_commands(working_set, command, is_subexpression);
if idx == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Any) {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
if let Some(let_env_decl_id) =
working_set.find_decl(b"let-env", &Type::Any)
working_set.find_decl(b"let-env", &Type::Nothing)
{
for element in pipeline.elements.iter_mut() {
if let PipelineElement::Expression(
@ -5823,7 +5818,7 @@ fn wrap_element_with_collect(
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
let span = expr.span;
if let Some(decl_id) = working_set.find_decl(b"collect", &Type::Any) {
if let Some(decl_id) = working_set.find_decl(b"collect", &Type::List(Box::new(Type::Any))) {
let mut output = vec![];
let var_id = IN_VARIABLE_ID;

View File

@ -22,8 +22,11 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
match (lhs, rhs) {
(Type::List(c), Type::List(d)) => type_compatible(c, d),
(Type::List(c), Type::Table(_)) => matches!(**c, Type::Any),
(Type::Number, Type::Int) => true,
(Type::Int, Type::Number) => true,
(Type::Number, Type::Float) => true,
(Type::Float, Type::Number) => true,
(Type::Closure, Type::Block) => true,
(Type::Any, _) => true,
(_, Type::Any) => true,
@ -47,6 +50,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::String, Type::String) => (Type::String, None),
(Type::Date, Type::Duration) => (Type::Date, None),
(Type::Duration, Type::Duration) => (Type::Duration, None),
@ -143,6 +151,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::Date, Type::Date) => (Type::Duration, None),
(Type::Date, Type::Duration) => (Type::Date, None),
(Type::Duration, Type::Duration) => (Type::Duration, None),
@ -185,6 +198,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Int, Type::Filesize) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
@ -241,6 +259,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
@ -280,6 +303,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
(Type::Float, Type::Float) => (Type::Float, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::Filesize, Type::Filesize) => (Type::Float, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
@ -324,6 +352,11 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Int, None),
(Type::Int, Type::Float) => (Type::Int, None),
(Type::Float, Type::Float) => (Type::Int, None),
(Type::Number, Type::Number) => (Type::Number, None),
(Type::Number, Type::Int) => (Type::Number, None),
(Type::Int, Type::Number) => (Type::Number, None),
(Type::Number, Type::Float) => (Type::Number, None),
(Type::Float, Type::Number) => (Type::Number, None),
(Type::Filesize, Type::Filesize) => (Type::Int, None),
(Type::Filesize, Type::Int) => (Type::Filesize, None),
(Type::Filesize, Type::Float) => (Type::Filesize, None),
@ -410,7 +443,13 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Float) => (Type::Bool, None),
(Type::Number, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Number) => (Type::Bool, None),
(Type::Duration, Type::Duration) => (Type::Bool, None),
(Type::Date, Type::Date) => (Type::Bool, None),
(Type::Filesize, Type::Filesize) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
@ -453,7 +492,13 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Float) => (Type::Bool, None),
(Type::Number, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Number) => (Type::Bool, None),
(Type::Duration, Type::Duration) => (Type::Bool, None),
(Type::Date, Type::Date) => (Type::Bool, None),
(Type::Filesize, Type::Filesize) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
@ -496,7 +541,13 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Float) => (Type::Bool, None),
(Type::Number, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Number) => (Type::Bool, None),
(Type::Duration, Type::Duration) => (Type::Bool, None),
(Type::Date, Type::Date) => (Type::Bool, None),
(Type::Filesize, Type::Filesize) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
@ -539,7 +590,13 @@ pub fn math_result_type(
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Float) => (Type::Bool, None),
(Type::Number, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Number) => (Type::Bool, None),
(Type::Number, Type::Float) => (Type::Bool, None),
(Type::Float, Type::Number) => (Type::Bool, None),
(Type::Duration, Type::Duration) => (Type::Bool, None),
(Type::Date, Type::Date) => (Type::Bool, None),
(Type::Filesize, Type::Filesize) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
@ -731,7 +788,7 @@ pub fn math_result_type(
},
Operator::Comparison(Comparison::In) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
(Type::String, Type::Record(_)) => (Type::Bool, None),
@ -769,7 +826,7 @@ pub fn math_result_type(
},
Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
(Type::String, Type::Record(_)) => (Type::Bool, None),