mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +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.")]
|
#[error("Type mismatch.")]
|
||||||
#[diagnostic(code(nu::parser::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.")]
|
#[error("Missing required flag.")]
|
||||||
#[diagnostic(code(nu::parser::missing_required_flag))]
|
#[diagnostic(code(nu::parser::missing_required_flag))]
|
||||||
|
|
|
@ -1029,7 +1029,6 @@ pub fn parse_internal_call(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig_idx = spans_idx;
|
|
||||||
let (arg, err) = parse_multispan_value(
|
let (arg, err) = parse_multispan_value(
|
||||||
working_set,
|
working_set,
|
||||||
&spans[..end],
|
&spans[..end],
|
||||||
|
@ -1040,7 +1039,6 @@ pub fn parse_internal_call(
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) {
|
let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) {
|
||||||
let span = span(&spans[orig_idx..spans_idx]);
|
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::TypeMismatch(
|
Some(ParseError::TypeMismatch(
|
||||||
positional.shape.to_type(),
|
positional.shape.to_type(),
|
||||||
|
@ -1048,7 +1046,7 @@ pub fn parse_internal_call(
|
||||||
arg.span,
|
arg.span,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
Expression::garbage(span)
|
Expression::garbage(arg.span)
|
||||||
} else {
|
} else {
|
||||||
arg
|
arg
|
||||||
};
|
};
|
||||||
|
@ -5853,6 +5851,7 @@ pub fn parse_record(
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
|
|
||||||
|
let mut field_types = Some(vec![]);
|
||||||
while idx < tokens.len() {
|
while idx < tokens.len() {
|
||||||
let (field, err) = parse_value(
|
let (field, err) = parse_value(
|
||||||
working_set,
|
working_set,
|
||||||
|
@ -5887,6 +5886,15 @@ pub fn parse_record(
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
idx += 1;
|
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));
|
output.push((field, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5894,7 +5902,11 @@ pub fn parse_record(
|
||||||
Expression {
|
Expression {
|
||||||
expr: Expr::Record(output),
|
expr: Expr::Record(output),
|
||||||
span,
|
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,
|
custom_completion: None,
|
||||||
},
|
},
|
||||||
error,
|
error,
|
||||||
|
|
|
@ -13,6 +13,22 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
|
||||||
(Type::Closure, Type::Block) => true,
|
(Type::Closure, Type::Block) => true,
|
||||||
(Type::Any, _) => true,
|
(Type::Any, _) => 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,
|
(lhs, rhs) => lhs == rhs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ fn number_int() -> TestResult {
|
||||||
run_test(r#"def foo [x:number] { $x }; foo 1"#, "1")
|
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]
|
#[test]
|
||||||
fn number_float() -> TestResult {
|
fn number_float() -> TestResult {
|
||||||
run_test(r#"def foo [x:number] { $x }; foo 1.4"#, "1.4")
|
run_test(r#"def foo [x:number] { $x }; foo 1.4"#, "1.4")
|
||||||
|
|
Loading…
Reference in a new issue