mirror of
https://github.com/nushell/nushell.git
synced 2025-02-22 05:21:44 +01:00
add type check during eval time (#11475)
# Description Fixes: #11438 Take the following as example: ```nushell def spam [foo: string] { $'foo: ($foo | describe)' } def outer [--foo: string] { spam $foo } outer ``` When we call `outer`, type checker only check the all for `outer`, but doesn't check inside the body of `outer`. This pr is trying to introduce a type checking process through `Type::is_subtype()` during eval time. ## NOTE I'm not really sure if it's easy to make a check inside the body of `outer`. Adding an eval time type checker seems like an easier solution. As a result: `outer` will be caught by runtime, not parse time type checker cc @kubouch # User-Facing Changes After this pr the following call will failed: ```nushell > outer Error: nu:🐚:cant_convert × Can't convert to string. ╭─[entry #27:1:1] 1 │ def outer [--foo: any] { 2 │ spam $foo · ──┬─ · ╰── can't convert nothing to string 3 │ } ╰──── ``` # Tests + Formatting Done # After Submitting NaN
This commit is contained in:
parent
8cad12a05c
commit
724818030d
@ -42,11 +42,17 @@ pub fn eval_call(
|
||||
|
||||
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
|
||||
|
||||
for (param_idx, param) in decl
|
||||
for (param_idx, (param, required)) in decl
|
||||
.signature()
|
||||
.required_positional
|
||||
.iter()
|
||||
.chain(decl.signature().optional_positional.iter())
|
||||
.map(|p| (p, true))
|
||||
.chain(
|
||||
decl.signature()
|
||||
.optional_positional
|
||||
.iter()
|
||||
.map(|p| (p, false)),
|
||||
)
|
||||
.enumerate()
|
||||
{
|
||||
let var_id = param
|
||||
@ -55,6 +61,14 @@ pub fn eval_call(
|
||||
|
||||
if let Some(arg) = call.positional_nth(param_idx) {
|
||||
let result = eval_expression(engine_state, caller_stack, arg)?;
|
||||
if required && !result.get_type().is_subtype(¶m.shape.to_type()) {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: param.shape.to_type().to_string(),
|
||||
from_type: result.get_type().to_string(),
|
||||
span: result.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
callee_stack.add_var(var_id, result);
|
||||
} else if let Some(value) = ¶m.default_value {
|
||||
callee_stack.add_var(var_id, value.to_owned());
|
||||
|
@ -40,11 +40,11 @@ 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().all(|(col_x, ty_x)| {
|
||||
if let Some((_, ty_y)) = that.iter().find(|(col_y, _)| col_x == col_y) {
|
||||
that.iter().all(|(col_y, ty_y)| {
|
||||
if let Some((_, ty_x)) = this.iter().find(|(col_x, _)| col_x == col_y) {
|
||||
ty_x.is_subtype(ty_y)
|
||||
} else {
|
||||
false
|
||||
|
@ -185,6 +185,7 @@ export def critical [
|
||||
--short (-s) # Whether to use a short prefix
|
||||
--format (-f): string # A format (for further reference: help std log)
|
||||
] {
|
||||
let format = $format | default ""
|
||||
handle-log $message (log-types | get CRITICAL) $format $short
|
||||
}
|
||||
|
||||
@ -194,6 +195,7 @@ export def error [
|
||||
--short (-s) # Whether to use a short prefix
|
||||
--format (-f): string # A format (for further reference: help std log)
|
||||
] {
|
||||
let format = $format | default ""
|
||||
handle-log $message (log-types | get ERROR) $format $short
|
||||
}
|
||||
|
||||
@ -203,6 +205,7 @@ export def warning [
|
||||
--short (-s) # Whether to use a short prefix
|
||||
--format (-f): string # A format (for further reference: help std log)
|
||||
] {
|
||||
let format = $format | default ""
|
||||
handle-log $message (log-types | get WARNING) $format $short
|
||||
}
|
||||
|
||||
@ -212,6 +215,7 @@ export def info [
|
||||
--short (-s) # Whether to use a short prefix
|
||||
--format (-f): string # A format (for further reference: help std log)
|
||||
] {
|
||||
let format = $format | default ""
|
||||
handle-log $message (log-types | get INFO) $format $short
|
||||
}
|
||||
|
||||
@ -221,6 +225,7 @@ export def debug [
|
||||
--short (-s) # Whether to use a short prefix
|
||||
--format (-f): string # A format (for further reference: help std log)
|
||||
] {
|
||||
let format = $format | default ""
|
||||
handle-log $message (log-types | get DEBUG) $format $short
|
||||
}
|
||||
|
||||
|
@ -214,3 +214,18 @@ fn infinite_recursion_does_not_panic() {
|
||||
"#);
|
||||
assert!(actual.err.contains("Recursion limit (50) reached"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_check_for_during_eval() -> TestResult {
|
||||
fail_test(
|
||||
r#"def spam [foo: string] { $foo | describe }; def outer [--foo: string] { spam $foo }; outer"#,
|
||||
"can't convert nothing to string",
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn type_check_for_during_eval2() -> TestResult {
|
||||
fail_test(
|
||||
r#"def spam [foo: string] { $foo | describe }; def outer [--foo: any] { spam $foo }; outer"#,
|
||||
"can't convert nothing to string",
|
||||
)
|
||||
}
|
||||
|
@ -114,6 +114,14 @@ fn record_subtyping_allows_general_inner() -> TestResult {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_subtyping_works() -> TestResult {
|
||||
run_test(
|
||||
r#"def merge_records [other: record<bar: int>] { "" }; merge_records {"bar": 3, "foo": 4}"#,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transpose_into_load_env() -> TestResult {
|
||||
run_test(
|
||||
|
Loading…
Reference in New Issue
Block a user