Skip to content

Commit

Permalink
feat: add return expression (#712)
Browse files Browse the repository at this point in the history
* feat: add return expression

* feat: add returns to `TypeDef`

* feat(vrl): add possible return types to type information

* feat(vrl): check return types in closure blocks

* feat(vrl): handle explicit returns in closure blocks

* fix: ui tests

* Update changelog.d/712.feature.md

Co-authored-by: Jesse Szwedko <jesse@szwedko.me>

---------

Co-authored-by: Jesse Szwedko <jesse@szwedko.me>
  • Loading branch information
mladedav and jszwedko committed Mar 18, 2024
1 parent f2d71cd commit 81d1376
Show file tree
Hide file tree
Showing 34 changed files with 553 additions and 74 deletions.
1 change: 1 addition & 0 deletions changelog.d/712.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added the `return` expression as per [RFC 7496](https://github.com/vectordotdev/vector/blob/4671ccbf0a6359ef8b752fa99fae9eb9c60fdee5/rfcs/2023-02-08-7496-vrl-return.md). This expression can be used to terminate the VRL program early while still emitting a value.
2 changes: 1 addition & 1 deletion lib/tests/tests/diagnostics/syntax_error_path_segment.vrl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# │ ^
# │ │
# │ unexpected end of query path
# │ expected one of: "(", "abort", "identifier", "path field", "string literal"
# │ expected one of: "(", "abort", "identifier", "path field", "return", "string literal"
# │
# = see language documentation at https://vrl.dev
# = try your code in the VRL REPL, learn more at https://vrl.dev/examples
Expand Down
34 changes: 26 additions & 8 deletions lib/tests/tests/expressions/abort/abort_typedef.vrl
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
# result: {
# "a": {"never": true},
# "b": {"bytes": true},
# "b2": {"bytes": true},
# "c": {"never": true},
# "d": {"object": {}},
# "e": {"null": true},
# "f": {"bytes": true, "integer": true},
# "g": {"never": true}
# "a": {
# "never": true
# },
# "b": {
# "bytes": true
# },
# "b2": {
# "bytes": true
# },
# "c": {
# "never": true
# },
# "d": {
# "object": {}
# },
# "e": {
# "null": true
# },
# "f": {
# "bytes": true,
# "integer": true
# },
# "g": {
# "bytes": true
# }
# }


x = "string"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# result: { "foo": true }

.foo = true
return .
.foo = false
abort "unreachable"
4 changes: 4 additions & 0 deletions lib/tests/tests/expressions/return/return.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# result: { "foo": true }

.foo = true
return .
24 changes: 24 additions & 0 deletions lib/tests/tests/expressions/return/return_bad_type_in_closure.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# DIAGNOSTICS
# result:
#
# error[E122]: type mismatch in closure return type
# ┌─ :2:37
# │
# 2 │ filter([1, 2, 3]) -> |_key, _value| {
# │ ╭─────────────────────────────────────────^
# │ │ ╭───────────────────────────────────────'
# │ │ │ ╭─────────────────────────────────────'
# 3 │ │ │ │ return 0
# 4 │ │ │ │ true
# 5 │ │ │ │ }
# │ ╰─│─│─^ block returns invalid value type
# │ ╰─│─' expected: boolean
# │ ╰─' received: integer or boolean
# │
# = see language documentation at https://vrl.dev
# = try your code in the VRL REPL, learn more at https://vrl.dev/examples

filter([1, 2, 3]) -> |_key, _value| {
return 0
true
}
5 changes: 5 additions & 0 deletions lib/tests/tests/expressions/return/return_custom.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# result: { "foo": true, "bar": 42 }

ret.foo = true
ret.bar = 42
return ret
10 changes: 10 additions & 0 deletions lib/tests/tests/expressions/return/return_in_closure.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# result: 0

foo = 0

for_each({ "foo": "bar", "lorem": "ipsum" }) -> |_key, _value| {
return 42
foo = 1
}

foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# result: null

for_each({ "foo": "bar", "lorem": "ipsum" }) -> |_key, _value| {

for_each({ "foo": "bar", "lorem": "ipsum" }) -> |_key, _value| {
true
}

return 42
}
10 changes: 10 additions & 0 deletions lib/tests/tests/expressions/return/return_in_closure_argument.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# result: [0, 1]

foo = 0
bar = 0

for_each({ "foo": for_each({}) -> |_key, _value| { foo = 1; return true } }) -> |_key, _value| {
bar = 1
}

[foo, bar]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# result: {}

filter({ "bar": null }) -> |_key, _value| {
for_each({ "foo": null }) -> |_key, _value| {
return 0
}

false
}
3 changes: 3 additions & 0 deletions lib/tests/tests/expressions/return/return_true.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# result: true

return true
50 changes: 50 additions & 0 deletions lib/tests/tests/expressions/return/return_typedef.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# result: {
# "a": {
# "never": true
# },
# "b": {
# "bytes": true
# },
# "b2": {
# "bytes": true
# },
# "c": {
# "never": true
# },
# "d": {
# "object": {}
# },
# "e": {
# "null": true
# },
# "f": {
# "bytes": true,
# "integer": true
# },
# "g": {
# "bytes": true
# }
# }

x = "string"

.a = type_def({return .})
.b = type_def({if false {return .} else {"string"}})
.b2 = type_def({if true {return .} else {"string"}})
.c = type_def({return {if false {abort} else {"string"}}})
.d = type_def({{} | {return .}})
.e = type_def({ if false {return .} })
.f = type_def({
x = "string"
if false {
return .
} else {
x = 3
}
x
})
.g = type_def({
return .
"string"
})
.
9 changes: 1 addition & 8 deletions lib/tests/tests/functions/parse_groks.vrl
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
# object: { "message": "username=Hupsakee" }
# result: { "username": "Hupsakee" }

. = parse_groks!(
.message,
patterns: [ "%{TEST}" ],
alias_sources: [ "lib/tests/tests/functions/parse_groks_alias_source.json" ]
)
# result: null
40 changes: 40 additions & 0 deletions lib/tests/tests/internal/reserved_identifiers.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# result: { "abort": true, "all": true, "any": true, "array": true, "bool": true, "boolean": true, "break": true, "continue": true, "do": true, "duration": true, "each": true, "else": true, "emit": true, "false": true, "floa": true, "for": true, "forall": true, "foreach": true, "if": true, "int": true, "integer": true, "iter": true, "loop": true, "null": true, "object": true, "regex": true, "return": true, "string": true, "timestamp": true, "travers": true, "true": true, "try": true, "undefine": true, "unless": true, "walk": true, "while": true }

.abort = true
.all = true
.any = true
.array = true
.bool = true
.boolean = true
.break = true
.continue = true
.do = true
.duration = true
.each = true
.else = true
.emit = true
.false = true
.floa = true
.for = true
.forall = true
.foreach = true
.if = true
.int = true
.integer = true
.iter = true
.loop = true
.null = true
.object = true
.regex = true
.return = true
.string = true
.timestamp = true
.travers = true
.true = true
.try = true
.undefine = true
.unless = true
.walk = true
.while = true

.
18 changes: 15 additions & 3 deletions src/compiler/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::compiler::{
expression::{
assignment, function_call, literal, predicate, query, Abort, Array, Assignment, Block,
Container, Expr, Expression, FunctionArgument, FunctionCall, Group, IfStatement, Literal,
Noop, Not, Object, Op, Predicate, Query, Target, Unary, Variable,
Noop, Not, Object, Op, Predicate, Query, Return, Target, Unary, Variable,
},
parser::ast::RootExpr,
program::ProgramInfo,
Expand Down Expand Up @@ -150,8 +150,8 @@ impl<'a> Compiler<'a> {

fn compile_expr(&mut self, node: Node<ast::Expr>, state: &mut TypeState) -> Option<Expr> {
use ast::Expr::{
Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Op, Query, Unary,
Variable,
Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Op, Query, Return,
Unary, Variable,
};
let original_state = state.clone();

Expand All @@ -168,6 +168,7 @@ impl<'a> Compiler<'a> {
Variable(node) => self.compile_variable(node, state).map(Into::into),
Unary(node) => self.compile_unary(node, state).map(Into::into),
Abort(node) => self.compile_abort(node, state).map(Into::into),
Return(node) => self.compile_return(node, state).map(Into::into),
}?;

// If the previously compiled expression is fallible, _and_ we are
Expand Down Expand Up @@ -816,6 +817,17 @@ impl<'a> Compiler<'a> {
.ok()
}

fn compile_return(&mut self, node: Node<ast::Return>, state: &mut TypeState) -> Option<Return> {
let (span, r#return) = node.take();

let expr = self.compile_expr(*r#return.expr, state)?;
let node = Node::new(span, expr);

Return::new(span, node, state)
.map_err(|err| self.diagnostics.push(Box::new(err)))
.ok()
}

fn handle_parser_error(&mut self, error: crate::parser::Error) {
self.diagnostics.push(Box::new(error));
}
Expand Down
Loading

0 comments on commit 81d1376

Please sign in to comment.