mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Improve inferred record types and type compat (#8649)
# Description This allows for type inference to infer record types in more cases. The only time we will now fall back to `Any` is when one of the fields has a computed value. I also updated the type mismatch error and highlighting to be in-line with other errors. # User-Facing Changes This may result in stricter type checking. Previously `{}` had the inferred type `Any` but will now have the correct inferred type of `Record<>`. # 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` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **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.
This commit is contained in:
parent
da8cb14f8b
commit
393717dbb4
4 changed files with 38 additions and 5 deletions
|
@ -307,7 +307,7 @@ pub enum ParseError {
|
|||
|
||||
#[error("Type mismatch.")]
|
||||
#[diagnostic(code(nu::parser::type_mismatch))]
|
||||
TypeMismatch(Type, Type, #[label("expected {0:?}, found {1:?}")] Span), // expected, found, span
|
||||
TypeMismatch(Type, Type, #[label("expected {0}, found {1}")] Span), // expected, found, span
|
||||
|
||||
#[error("Missing required flag.")]
|
||||
#[diagnostic(code(nu::parser::missing_required_flag))]
|
||||
|
|
|
@ -1029,7 +1029,6 @@ pub fn parse_internal_call(
|
|||
continue;
|
||||
}
|
||||
|
||||
let orig_idx = spans_idx;
|
||||
let (arg, err) = parse_multispan_value(
|
||||
working_set,
|
||||
&spans[..end],
|
||||
|
@ -1040,7 +1039,6 @@ pub fn parse_internal_call(
|
|||
error = error.or(err);
|
||||
|
||||
let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) {
|
||||
let span = span(&spans[orig_idx..spans_idx]);
|
||||
error = error.or_else(|| {
|
||||
Some(ParseError::TypeMismatch(
|
||||
positional.shape.to_type(),
|
||||
|
@ -1048,7 +1046,7 @@ pub fn parse_internal_call(
|
|||
arg.span,
|
||||
))
|
||||
});
|
||||
Expression::garbage(span)
|
||||
Expression::garbage(arg.span)
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
|
@ -5853,6 +5851,7 @@ pub fn parse_record(
|
|||
let mut output = vec![];
|
||||
let mut idx = 0;
|
||||
|
||||
let mut field_types = Some(vec![]);
|
||||
while idx < tokens.len() {
|
||||
let (field, err) = parse_value(
|
||||
working_set,
|
||||
|
@ -5887,6 +5886,15 @@ pub fn parse_record(
|
|||
error = error.or(err);
|
||||
idx += 1;
|
||||
|
||||
if let Some(field) = field.as_string() {
|
||||
if let Some(fields) = &mut field_types {
|
||||
fields.push((field, value.ty.clone()));
|
||||
}
|
||||
} else {
|
||||
// We can't properly see all the field types
|
||||
// so fall back to the Any type later
|
||||
field_types = None;
|
||||
}
|
||||
output.push((field, value));
|
||||
}
|
||||
|
||||
|
@ -5894,7 +5902,11 @@ pub fn parse_record(
|
|||
Expression {
|
||||
expr: Expr::Record(output),
|
||||
span,
|
||||
ty: Type::Any, //FIXME: but we don't know the contents of the fields, do we?
|
||||
ty: (if let Some(fields) = field_types {
|
||||
Type::Record(fields)
|
||||
} else {
|
||||
Type::Any
|
||||
}),
|
||||
custom_completion: None,
|
||||
},
|
||||
error,
|
||||
|
|
|
@ -13,6 +13,22 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
|
|||
(Type::Closure, Type::Block) => true,
|
||||
(Type::Any, _) => true,
|
||||
(_, Type::Any) => true,
|
||||
(Type::Record(fields_lhs), Type::Record(fields_rhs)) => {
|
||||
// Structural subtyping
|
||||
'outer: for field_lhs in fields_lhs {
|
||||
for field_rhs in fields_rhs {
|
||||
if field_lhs.0 == field_rhs.0 {
|
||||
if type_compatible(&field_lhs.1, &field_rhs.1) {
|
||||
continue 'outer;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
(lhs, rhs) => lhs == rhs,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,11 @@ fn number_int() -> TestResult {
|
|||
run_test(r#"def foo [x:number] { $x }; foo 1"#, "1")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_record_mismatch() -> TestResult {
|
||||
fail_test(r#"def foo [x:int] { $x }; foo {}"#, "expected int")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_float() -> TestResult {
|
||||
run_test(r#"def foo [x:number] { $x }; foo 1.4"#, "1.4")
|
||||
|
|
Loading…
Reference in a new issue