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

@ -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;