forked from extern/nushell
improve subtyping (#9614)
# Description the current subtyping rule needs you to define the record entries in the same order as declared in the annotation. this pr improves that now ```nushell { name: 'Him', age: 12 } # , { age: 100, name: 'It' } # and { name: 'Red', age: 69, height: "5-8" } # will all match record<name: string, age: int> # previously only the first one would match ``` however, something like ```nushell { name: 'Her' } # will not # and { name: 'Car', wheels: 5 } ``` EDIT: applied JT's suggestion
This commit is contained in:
parent
6c8adac0d9
commit
544c46e0e4
@ -7,16 +7,18 @@ use nu_protocol::{
|
|||||||
pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
|
pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
|
||||||
// Structural subtyping
|
// Structural subtyping
|
||||||
let is_compatible = |expected: &[(String, Type)], found: &[(String, Type)]| {
|
let is_compatible = |expected: &[(String, Type)], found: &[(String, Type)]| {
|
||||||
// the expected type is `any`
|
|
||||||
if expected.is_empty() {
|
if expected.is_empty() {
|
||||||
true
|
true
|
||||||
} else if expected.len() != found.len() {
|
} else if expected.len() > found.len() {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
expected
|
expected.iter().all(|(col_x, ty_x)| {
|
||||||
.iter()
|
if let Some((_, ty_y)) = found.iter().find(|(col_y, _)| col_x == col_y) {
|
||||||
.zip(found.iter())
|
type_compatible(ty_x, ty_y)
|
||||||
.all(|(lhs, rhs)| lhs.0 == rhs.0 && type_compatible(&lhs.1, &rhs.1))
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,12 +41,16 @@ impl Type {
|
|||||||
let is_subtype_collection = |this: &[(String, Type)], that: &[(String, Type)]| {
|
let is_subtype_collection = |this: &[(String, Type)], that: &[(String, Type)]| {
|
||||||
if this.is_empty() || that.is_empty() {
|
if this.is_empty() || that.is_empty() {
|
||||||
true
|
true
|
||||||
} else if this.len() != that.len() {
|
} else if this.len() > that.len() {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
this.iter()
|
this.iter().all(|(col_x, ty_x)| {
|
||||||
.zip(that.iter())
|
if let Some((_, ty_y)) = that.iter().find(|(col_y, _)| col_x == col_y) {
|
||||||
.all(|(lhs, rhs)| lhs.0 == rhs.0 && lhs.1.is_subtype(&rhs.1))
|
ty_x.is_subtype(ty_y)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,3 +59,30 @@ fn block_not_first_class_let() -> TestResult {
|
|||||||
"Blocks are not support as first-class values",
|
"Blocks are not support as first-class values",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_subtyping() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"def test [rec: record<name: string, age: int>] { $rec | describe };
|
||||||
|
test { age: 4, name: 'John' }",
|
||||||
|
"record<age: int, name: string>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_subtyping_2() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"def test [rec: record<name: string, age: int>] { $rec | describe };
|
||||||
|
test { age: 4, name: 'John', height: '5-9' }",
|
||||||
|
"record<age: int, name: string, height: string>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_subtyping_3() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
"def test [rec: record<name: string, age: int>] { $rec | describe };
|
||||||
|
test { name: 'Nu' }",
|
||||||
|
"expected",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user