diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index ea67015570..37991cf614 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -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))] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1c8cfe8c85..1246ff9867 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -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, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 5b51f8d636..68d47410a8 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -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, } } diff --git a/src/tests/test_type_check.rs b/src/tests/test_type_check.rs index dfac8a3e2a..73a85d3811 100644 --- a/src/tests/test_type_check.rs +++ b/src/tests/test_type_check.rs @@ -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")