diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index aa5ffce1b3..bef8748510 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -7,16 +7,18 @@ use nu_protocol::{ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { // Structural subtyping let is_compatible = |expected: &[(String, Type)], found: &[(String, Type)]| { - // the expected type is `any` if expected.is_empty() { true - } else if expected.len() != found.len() { + } else if expected.len() > found.len() { false } else { - expected - .iter() - .zip(found.iter()) - .all(|(lhs, rhs)| lhs.0 == rhs.0 && type_compatible(&lhs.1, &rhs.1)) + expected.iter().all(|(col_x, ty_x)| { + if let Some((_, ty_y)) = found.iter().find(|(col_y, _)| col_x == col_y) { + type_compatible(ty_x, ty_y) + } else { + false + } + }) } }; diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index ba4f258543..88924b505c 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -41,12 +41,16 @@ impl Type { let is_subtype_collection = |this: &[(String, Type)], that: &[(String, Type)]| { if this.is_empty() || that.is_empty() { true - } else if this.len() != that.len() { + } else if this.len() > that.len() { false } else { - this.iter() - .zip(that.iter()) - .all(|(lhs, rhs)| lhs.0 == rhs.0 && lhs.1.is_subtype(&rhs.1)) + this.iter().all(|(col_x, ty_x)| { + if let Some((_, ty_y)) = that.iter().find(|(col_y, _)| col_x == col_y) { + ty_x.is_subtype(ty_y) + } else { + false + } + }) } }; diff --git a/src/tests/test_type_check.rs b/src/tests/test_type_check.rs index 17c9f879b1..62194bf146 100644 --- a/src/tests/test_type_check.rs +++ b/src/tests/test_type_check.rs @@ -59,3 +59,30 @@ fn block_not_first_class_let() -> TestResult { "Blocks are not support as first-class values", ) } + +#[test] +fn record_subtyping() -> TestResult { + run_test( + "def test [rec: record] { $rec | describe }; + test { age: 4, name: 'John' }", + "record", + ) +} + +#[test] +fn record_subtyping_2() -> TestResult { + run_test( + "def test [rec: record] { $rec | describe }; + test { age: 4, name: 'John', height: '5-9' }", + "record", + ) +} + +#[test] +fn record_subtyping_3() -> TestResult { + fail_test( + "def test [rec: record] { $rec | describe }; + test { name: 'Nu' }", + "expected", + ) +}