Skip to content

Commit fafbdb2

Browse files
authored
chore(remap): improve type error message (#6483)
1 parent f09bf47 commit fafbdb2

18 files changed

+157
-27
lines changed

lib/vrl/compiler/src/expression/function_argument.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::expression::Expr;
22
use crate::parser::{Ident, Node};
33
use crate::{Parameter, Span};
4+
use std::fmt;
45
use std::ops::Deref;
56

67
#[derive(Debug, PartialEq)]
@@ -36,6 +37,12 @@ impl FunctionArgument {
3637
}
3738
}
3839

40+
impl fmt::Display for FunctionArgument {
41+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42+
self.expr.fmt(f)
43+
}
44+
}
45+
3946
impl Deref for FunctionArgument {
4047
type Target = Node<Expr>;
4148

lib/vrl/compiler/src/expression/function_call.rs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::expression::{FunctionArgument, Noop};
2-
use crate::function::ArgumentList;
2+
use crate::function::{ArgumentList, Parameter};
33
use crate::parser::{Ident, Node};
44
use crate::{value::Kind, Context, Expression, Function, Resolved, Span, State, TypeDef};
55
use diagnostic::{DiagnosticError, Label, Note};
@@ -120,10 +120,12 @@ impl FunctionCall {
120120
let expr_kind = argument.type_def(state).kind();
121121
if !parameter.kind().contains(expr_kind) {
122122
return Err(Error::InvalidArgumentKind {
123-
keyword: parameter.keyword,
123+
function_ident: function.identifier(),
124+
abort_on_error,
125+
arguments_fmt,
126+
parameter: *parameter,
124127
got: expr_kind,
125-
expected: parameter.kind(),
126-
expr_span: argument.span(),
128+
argument,
127129
argument_span,
128130
});
129131
}
@@ -266,6 +268,7 @@ impl PartialEq for FunctionCall {
266268
// -----------------------------------------------------------------------------
267269

268270
#[derive(thiserror::Error, Debug)]
271+
#[allow(clippy::large_enum_variant)]
269272
pub enum Error {
270273
#[error("call to undefined function")]
271274
Undefined {
@@ -302,10 +305,12 @@ pub enum Error {
302305

303306
#[error("invalid argument type")]
304307
InvalidArgumentKind {
305-
keyword: &'static str,
308+
function_ident: &'static str,
309+
abort_on_error: bool,
310+
arguments_fmt: Vec<String>,
311+
parameter: Parameter,
306312
got: Kind,
307-
expected: Kind,
308-
expr_span: Span,
313+
argument: FunctionArgument,
309314
argument_span: Span,
310315
},
311316

@@ -427,12 +432,16 @@ impl DiagnosticError for Error {
427432
}
428433

429434
InvalidArgumentKind {
430-
expr_span,
431-
argument_span,
432-
keyword,
435+
parameter,
433436
got,
434-
expected,
437+
argument,
438+
argument_span,
439+
..
435440
} => {
441+
let keyword = parameter.keyword;
442+
let expected = parameter.kind();
443+
let expr_span = argument.span();
444+
436445
// TODO: extract this out into a helper
437446
let kind_str = |kind: &Kind| {
438447
if kind.is_any() {
@@ -453,7 +462,7 @@ impl DiagnosticError for Error {
453462
format!(
454463
r#"but the parameter "{}" expects {}"#,
455464
keyword,
456-
kind_str(expected)
465+
kind_str(&expected)
457466
),
458467
argument_span,
459468
),
@@ -475,6 +484,70 @@ impl DiagnosticError for Error {
475484

476485
match self {
477486
AbortInfallible { .. } | FallibleArgument { .. } => vec![Note::SeeErrorDocs],
487+
InvalidArgumentKind {
488+
function_ident,
489+
abort_on_error,
490+
arguments_fmt,
491+
parameter,
492+
argument,
493+
..
494+
} => {
495+
// TODO: move this into a generic helper function
496+
let guard = match parameter.kind() {
497+
Kind::Bytes => format!("string!({})", argument),
498+
Kind::Integer => format!("int!({})", argument),
499+
Kind::Float => format!("float!({})", argument),
500+
Kind::Boolean => format!("bool!({})", argument),
501+
Kind::Object => format!("object!({})", argument),
502+
Kind::Array => format!("array!({})", argument),
503+
Kind::Timestamp => format!("timestamp!({})", argument),
504+
_ => return vec![],
505+
};
506+
507+
let coerce = match parameter.kind() {
508+
Kind::Bytes => Some(format!(r#"to_string({}) ?? "default""#, argument)),
509+
Kind::Integer => Some(format!("to_int({}) ?? 0", argument)),
510+
Kind::Float => Some(format!("to_float({}) ?? 0", argument)),
511+
Kind::Boolean => Some(format!("to_bool({}) ?? false", argument)),
512+
Kind::Timestamp => Some(format!("to_timestamp({}) ?? now()", argument)),
513+
_ => None,
514+
};
515+
516+
let args = {
517+
let mut args = String::new();
518+
let mut iter = arguments_fmt.iter().peekable();
519+
while let Some(arg) = iter.next() {
520+
args.push_str(arg);
521+
if iter.peek().is_some() {
522+
args.push_str(", ");
523+
}
524+
}
525+
526+
args
527+
};
528+
529+
let abort = if *abort_on_error { "!" } else { "" };
530+
531+
let mut notes = vec![];
532+
533+
let call = format!("{}{}({}))", function_ident, abort, args);
534+
535+
notes.append(&mut Note::solution(
536+
"guard against invalid type at runtime",
537+
vec![format!("{} = {}", argument, guard), call.clone()],
538+
));
539+
540+
if let Some(coerce) = coerce {
541+
notes.append(&mut Note::solution(
542+
"coerce with default value",
543+
vec![format!("{} = {}", argument, coerce), call],
544+
))
545+
}
546+
547+
notes.push(Note::SeeErrorDocs);
548+
549+
notes
550+
}
478551
_ => vec![],
479552
}
480553
}

lib/vrl/diagnostic/src/diagnostic.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ impl From<Box<dyn DiagnosticError>> for Diagnostic {
118118
impl From<Diagnostic> for diagnostic::Diagnostic<()> {
119119
fn from(diag: Diagnostic) -> Self {
120120
let mut notes = diag.notes.to_vec();
121-
notes.push(Note::SeeLangDocs);
122121

123122
// not all codes have a page on the site yet
124123
if diag.code >= 100 && diag.code <= 110 {
125124
notes.push(Note::SeeCodeDocs(diag.code));
126125
}
127126

127+
notes.push(Note::SeeLangDocs);
128+
128129
diagnostic::Diagnostic {
129130
severity: diag.severity.into(),
130131
code: Some(format!("E{:03}", diag.code)),

lib/vrl/diagnostic/src/note.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ pub enum Note {
1111

1212
#[doc(hidden)]
1313
SeeDocs(String, String),
14+
#[doc(hidden)]
15+
Basic(String),
16+
}
17+
18+
impl Note {
19+
pub fn solution(title: impl Into<String>, content: Vec<impl Into<String>>) -> Vec<Self> {
20+
let mut notes = vec![Self::Basic(format!("try: {}", title.into()))];
21+
22+
notes.push(Self::Basic(" ".to_owned()));
23+
for line in content {
24+
notes.push(Self::Basic(format!(" {}", line.into())));
25+
}
26+
notes.push(Self::Basic(" ".to_owned()));
27+
notes
28+
}
1429
}
1530

1631
impl fmt::Display for Note {
@@ -35,6 +50,7 @@ impl fmt::Display for Note {
3550
kind, path
3651
)
3752
}
53+
Basic(string) => write!(f, "{}", string),
3854
}
3955
}
4056
}

lib/vrl/tests/tests/diagnostics/call_to_undefined_function.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# │ undefined function
1010
# │ did you mean "parse_duration"?
1111
# │
12-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1312
# = learn more at: https://errors.vrl.dev/105
13+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1414

1515
unknown_function(.foo, .bar)

lib/vrl/tests/tests/diagnostics/function_argument_arity_mismatch.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# │ too many function arguments
1010
# │ this function takes a maximum of 1 argument
1111
# │
12-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1312
# = learn more at: https://errors.vrl.dev/106
13+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1414

1515
to_string("foo", "bar")

lib/vrl/tests/tests/diagnostics/function_argument_missing.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# 2 │ to_string()
77
# │ ^^^^^^^^^^^ required argument missing: "value" (position 0)
88
# │
9-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
109
# = learn more at: https://errors.vrl.dev/107
10+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1111

1212
to_string()

lib/vrl/tests/tests/diagnostics/invalid_if_condition_type.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
# │ instead it resolves to "string"
1111
# │
1212
# = hint: coerce the value using one of the coercion functions
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1413
# = learn more at: https://errors.vrl.dev/102
14+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1515

1616
if "nope" {
1717
true

lib/vrl/tests/tests/diagnostics/invalid_regex.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# 2 │ r'['
77
# │ ^^^^ regex parse error: unclosed character class: [
88
# │
9-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
109
# = learn more at: https://errors.vrl.dev/101
10+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1111

1212
r'['

lib/vrl/tests/tests/diagnostics/regex_parsing_unsuccessful.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# 2 │ r'['
77
# │ ^^^^ regex parse error: unclosed character class: [
88
# │
9-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
109
# = learn more at: https://errors.vrl.dev/101
10+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1111

1212
r'['

lib/vrl/tests/tests/diagnostics/unhandled_expression_error.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# │ handle the error case to ensure runtime success
1111
# │
1212
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1413
# = learn more at: https://errors.vrl.dev/100
14+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1515

1616
1 / 0

lib/vrl/tests/tests/diagnostics/unhandled_function_error.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# │ handle the error case to ensure runtime success
1111
# │
1212
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1413
# = learn more at: https://errors.vrl.dev/100
14+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1515

1616
parse_json("true")

lib/vrl/tests/tests/diagnostics/unhandled_parse_regex_all_type.vrl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@
1010
# │ this expression resolves to one of "string" or "null"
1111
# │ but the parameter "value" expects the exact type "string"
1212
# │
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
13+
# = try: guard against invalid type at runtime
14+
# =
15+
# = .result[0].an = string!(.result[0].an)
16+
# = sha3(.result[0].an))
17+
# =
18+
# = try: coerce with default value
19+
# =
20+
# = .result[0].an = to_string(.result[0].an) ?? "default"
21+
# = sha3(.result[0].an))
22+
# =
23+
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
1424
# = learn more at: https://errors.vrl.dev/110
25+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1526

1627
.result = parse_regex_all!(.message, r'(?P<an>an.)')
1728
.a = sha3(.result[0].an)

lib/vrl/tests/tests/diagnostics/unknown_function_argument_keyword.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# │ │
99
# │ this function accepts the following keywords: "value"
1010
# │
11-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1211
# = learn more at: https://errors.vrl.dev/108
12+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1313

1414
to_string(foo: "foo")

lib/vrl/tests/tests/diagnostics/unneeded_error_assignment.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# │ use: ok = 5
1111
# │
1212
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1413
# = learn more at: https://errors.vrl.dev/104
14+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1515

1616
ok, err = 5;

lib/vrl/tests/tests/diagnostics/unsuccessful_parse_json_type.vrl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@
1010
# │ this expression resolves to "unknown type"
1111
# │ but the parameter "value" expects the exact type "string"
1212
# │
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
13+
# = try: guard against invalid type at runtime
14+
# =
15+
# = result.message = string!(result.message)
16+
# = sha3(result.message))
17+
# =
18+
# = try: coerce with default value
19+
# =
20+
# = result.message = to_string(result.message) ?? "default"
21+
# = sha3(result.message))
22+
# =
23+
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
1424
# = learn more at: https://errors.vrl.dev/110
25+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1526

1627
.message = to_string!(.message)
1728
result = parse_json!(.message)

lib/vrl/tests/tests/internal/infallible_ok_maybe_null.vrl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@
1010
# │ this expression resolves to one of "string" or "null"
1111
# │ but the parameter "value" expects the exact type "string"
1212
# │
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
13+
# = try: guard against invalid type at runtime
14+
# =
15+
# = .foo = string!(.foo)
16+
# = upcase(.foo))
17+
# =
18+
# = try: coerce with default value
19+
# =
20+
# = .foo = to_string(.foo) ?? "default"
21+
# = upcase(.foo))
22+
# =
23+
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
1424
# = learn more at: https://errors.vrl.dev/110
25+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1526

1627
.foo # any
1728
.foo, err = to_string(.foo) # string or null

lib/vrl/tests/tests/issues/6469_fallible_operations_marked_as_infallible.vrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# │ handle the error case to ensure runtime success
1111
# │
1212
# = see error handling documentation at: https://vector.dev/docs/reference/vrl/
13-
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1413
# = learn more at: https://errors.vrl.dev/100
14+
# = see language documentation at: https://vector.dev/docs/reference/vrl/
1515

1616
5 + to_int(.foo)

0 commit comments

Comments
 (0)