From 81d1376b8a6f7441f817f09aabfc161100a8fafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ml=C3=A1dek?= Date: Mon, 18 Mar 2024 15:03:18 +0100 Subject: [PATCH] feat: add return expression (#712) * 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 --------- Co-authored-by: Jesse Szwedko --- changelog.d/712.feature.md | 1 + .../diagnostics/syntax_error_path_segment.vrl | 2 +- .../tests/expressions/abort/abort_typedef.vrl | 34 +++-- .../return/expressions_after_return.vrl | 6 + lib/tests/tests/expressions/return/return.vrl | 4 + .../return/return_bad_type_in_closure.vrl | 24 ++++ .../expressions/return/return_custom.vrl | 5 + .../expressions/return/return_in_closure.vrl | 10 ++ .../return_in_closure_after_closure.vrl | 10 ++ .../return/return_in_closure_argument.vrl | 10 ++ .../return/return_in_closure_in_closure.vrl | 9 ++ .../tests/expressions/return/return_true.vrl | 3 + .../expressions/return/return_typedef.vrl | 50 ++++++++ lib/tests/tests/functions/parse_groks.vrl | 9 +- .../tests/internal/reserved_identifiers.vrl | 40 ++++++ src/compiler/compiler.rs | 18 ++- src/compiler/expression.rs | 24 +++- src/compiler/expression/abort.rs | 5 +- src/compiler/expression/array.rs | 24 +++- src/compiler/expression/block.rs | 10 +- src/compiler/expression/function_call.rs | 16 ++- src/compiler/expression/if_statement.rs | 16 ++- src/compiler/expression/not.rs | 4 +- src/compiler/expression/object.rs | 26 ++-- src/compiler/expression/return.rs | 119 ++++++++++++++++++ src/compiler/expression_error.rs | 10 +- src/compiler/function/closure.rs | 10 +- src/compiler/runtime.rs | 15 ++- src/compiler/type_def.rs | 27 ++++ src/compiler/unused_expression_checker.rs | 12 +- src/lib.rs | 12 +- src/parser/ast.rs | 32 ++++- src/parser/lex.rs | 16 ++- src/parser/parser.lalrpop | 14 +++ 34 files changed, 553 insertions(+), 74 deletions(-) create mode 100644 changelog.d/712.feature.md create mode 100644 lib/tests/tests/expressions/return/expressions_after_return.vrl create mode 100644 lib/tests/tests/expressions/return/return.vrl create mode 100644 lib/tests/tests/expressions/return/return_bad_type_in_closure.vrl create mode 100644 lib/tests/tests/expressions/return/return_custom.vrl create mode 100644 lib/tests/tests/expressions/return/return_in_closure.vrl create mode 100644 lib/tests/tests/expressions/return/return_in_closure_after_closure.vrl create mode 100644 lib/tests/tests/expressions/return/return_in_closure_argument.vrl create mode 100644 lib/tests/tests/expressions/return/return_in_closure_in_closure.vrl create mode 100644 lib/tests/tests/expressions/return/return_true.vrl create mode 100644 lib/tests/tests/expressions/return/return_typedef.vrl create mode 100644 lib/tests/tests/internal/reserved_identifiers.vrl create mode 100644 src/compiler/expression/return.rs diff --git a/changelog.d/712.feature.md b/changelog.d/712.feature.md new file mode 100644 index 0000000000..24acd341ca --- /dev/null +++ b/changelog.d/712.feature.md @@ -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. diff --git a/lib/tests/tests/diagnostics/syntax_error_path_segment.vrl b/lib/tests/tests/diagnostics/syntax_error_path_segment.vrl index a8c596a82c..b68f057931 100644 --- a/lib/tests/tests/diagnostics/syntax_error_path_segment.vrl +++ b/lib/tests/tests/diagnostics/syntax_error_path_segment.vrl @@ -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 diff --git a/lib/tests/tests/expressions/abort/abort_typedef.vrl b/lib/tests/tests/expressions/abort/abort_typedef.vrl index ed51daa58d..1d8a47a34f 100644 --- a/lib/tests/tests/expressions/abort/abort_typedef.vrl +++ b/lib/tests/tests/expressions/abort/abort_typedef.vrl @@ -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" diff --git a/lib/tests/tests/expressions/return/expressions_after_return.vrl b/lib/tests/tests/expressions/return/expressions_after_return.vrl new file mode 100644 index 0000000000..a297bc105d --- /dev/null +++ b/lib/tests/tests/expressions/return/expressions_after_return.vrl @@ -0,0 +1,6 @@ +# result: { "foo": true } + +.foo = true +return . +.foo = false +abort "unreachable" diff --git a/lib/tests/tests/expressions/return/return.vrl b/lib/tests/tests/expressions/return/return.vrl new file mode 100644 index 0000000000..1716efc563 --- /dev/null +++ b/lib/tests/tests/expressions/return/return.vrl @@ -0,0 +1,4 @@ +# result: { "foo": true } + +.foo = true +return . diff --git a/lib/tests/tests/expressions/return/return_bad_type_in_closure.vrl b/lib/tests/tests/expressions/return/return_bad_type_in_closure.vrl new file mode 100644 index 0000000000..b37840a12e --- /dev/null +++ b/lib/tests/tests/expressions/return/return_bad_type_in_closure.vrl @@ -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 +} diff --git a/lib/tests/tests/expressions/return/return_custom.vrl b/lib/tests/tests/expressions/return/return_custom.vrl new file mode 100644 index 0000000000..c7b72bd0ba --- /dev/null +++ b/lib/tests/tests/expressions/return/return_custom.vrl @@ -0,0 +1,5 @@ +# result: { "foo": true, "bar": 42 } + +ret.foo = true +ret.bar = 42 +return ret diff --git a/lib/tests/tests/expressions/return/return_in_closure.vrl b/lib/tests/tests/expressions/return/return_in_closure.vrl new file mode 100644 index 0000000000..9ce32d85aa --- /dev/null +++ b/lib/tests/tests/expressions/return/return_in_closure.vrl @@ -0,0 +1,10 @@ +# result: 0 + +foo = 0 + +for_each({ "foo": "bar", "lorem": "ipsum" }) -> |_key, _value| { + return 42 + foo = 1 +} + +foo diff --git a/lib/tests/tests/expressions/return/return_in_closure_after_closure.vrl b/lib/tests/tests/expressions/return/return_in_closure_after_closure.vrl new file mode 100644 index 0000000000..482a18ffe8 --- /dev/null +++ b/lib/tests/tests/expressions/return/return_in_closure_after_closure.vrl @@ -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 +} diff --git a/lib/tests/tests/expressions/return/return_in_closure_argument.vrl b/lib/tests/tests/expressions/return/return_in_closure_argument.vrl new file mode 100644 index 0000000000..8dbffe1b78 --- /dev/null +++ b/lib/tests/tests/expressions/return/return_in_closure_argument.vrl @@ -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] diff --git a/lib/tests/tests/expressions/return/return_in_closure_in_closure.vrl b/lib/tests/tests/expressions/return/return_in_closure_in_closure.vrl new file mode 100644 index 0000000000..b22d9ef3b5 --- /dev/null +++ b/lib/tests/tests/expressions/return/return_in_closure_in_closure.vrl @@ -0,0 +1,9 @@ +# result: {} + +filter({ "bar": null }) -> |_key, _value| { + for_each({ "foo": null }) -> |_key, _value| { + return 0 + } + + false +} diff --git a/lib/tests/tests/expressions/return/return_true.vrl b/lib/tests/tests/expressions/return/return_true.vrl new file mode 100644 index 0000000000..520214b09c --- /dev/null +++ b/lib/tests/tests/expressions/return/return_true.vrl @@ -0,0 +1,3 @@ +# result: true + +return true diff --git a/lib/tests/tests/expressions/return/return_typedef.vrl b/lib/tests/tests/expressions/return/return_typedef.vrl new file mode 100644 index 0000000000..2d558544e2 --- /dev/null +++ b/lib/tests/tests/expressions/return/return_typedef.vrl @@ -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" +}) +. diff --git a/lib/tests/tests/functions/parse_groks.vrl b/lib/tests/tests/functions/parse_groks.vrl index f6f130b402..0fe766aec8 100644 --- a/lib/tests/tests/functions/parse_groks.vrl +++ b/lib/tests/tests/functions/parse_groks.vrl @@ -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 diff --git a/lib/tests/tests/internal/reserved_identifiers.vrl b/lib/tests/tests/internal/reserved_identifiers.vrl new file mode 100644 index 0000000000..9a47c7839c --- /dev/null +++ b/lib/tests/tests/internal/reserved_identifiers.vrl @@ -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 + +. diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index c3448369fa..e14e6294d1 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -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, @@ -150,8 +150,8 @@ impl<'a> Compiler<'a> { fn compile_expr(&mut self, node: Node, state: &mut TypeState) -> Option { 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(); @@ -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 @@ -816,6 +817,17 @@ impl<'a> Compiler<'a> { .ok() } + fn compile_return(&mut self, node: Node, state: &mut TypeState) -> Option { + 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)); } diff --git a/src/compiler/expression.rs b/src/compiler/expression.rs index a645659354..e498e1b328 100644 --- a/src/compiler/expression.rs +++ b/src/compiler/expression.rs @@ -19,6 +19,7 @@ pub use object::Object; pub use op::Op; pub use predicate::Predicate; pub use query::{Query, Target}; +pub use r#return::Return; pub use unary::Unary; pub use variable::Variable; @@ -39,6 +40,7 @@ mod noop; mod not; mod object; mod op; +mod r#return; pub(crate) mod unary; mod variable; @@ -115,6 +117,7 @@ pub enum Expr { Noop(Noop), Unary(Unary), Abort(Abort), + Return(Return), } impl Expr { @@ -122,7 +125,7 @@ impl Expr { use container::Variant::{Array, Block, Group, Object}; use Expr::{ Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Noop, Op, Query, - Unary, Variable, + Return, Unary, Variable, }; match self { @@ -142,6 +145,7 @@ impl Expr { Noop(..) => "noop", Unary(..) => "unary operation", Abort(..) => "abort operation", + Return(..) => "return", } } @@ -181,7 +185,7 @@ impl Expression for Expr { fn resolve(&self, ctx: &mut Context) -> Resolved { use Expr::{ Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Noop, Op, Query, - Unary, Variable, + Return, Unary, Variable, }; match self { @@ -196,13 +200,14 @@ impl Expression for Expr { Noop(v) => v.resolve(ctx), Unary(v) => v.resolve(ctx), Abort(v) => v.resolve(ctx), + Return(v) => v.resolve(ctx), } } fn resolve_constant(&self, state: &TypeState) -> Option { use Expr::{ Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Noop, Op, Query, - Unary, Variable, + Return, Unary, Variable, }; match self { @@ -217,13 +222,14 @@ impl Expression for Expr { Noop(v) => Expression::resolve_constant(v, state), Unary(v) => Expression::resolve_constant(v, state), Abort(v) => Expression::resolve_constant(v, state), + Return(v) => Expression::resolve_constant(v, state), } } fn type_info(&self, state: &TypeState) -> TypeInfo { use Expr::{ Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Noop, Op, Query, - Unary, Variable, + Return, Unary, Variable, }; match self { @@ -238,6 +244,7 @@ impl Expression for Expr { Noop(v) => v.type_info(state), Unary(v) => v.type_info(state), Abort(v) => v.type_info(state), + Return(v) => v.type_info(state), } } } @@ -246,7 +253,7 @@ impl fmt::Display for Expr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Expr::{ Abort, Assignment, Container, FunctionCall, IfStatement, Literal, Noop, Op, Query, - Unary, Variable, + Return, Unary, Variable, }; match self { @@ -261,6 +268,7 @@ impl fmt::Display for Expr { Noop(v) => v.fmt(f), Unary(v) => v.fmt(f), Abort(v) => v.fmt(f), + Return(v) => v.fmt(f), } } } @@ -333,6 +341,12 @@ impl From for Expr { } } +impl From for Expr { + fn from(r#return: Return) -> Self { + Expr::Return(r#return) + } +} + impl From for Expr { fn from(value: Value) -> Self { use std::collections::BTreeMap; diff --git a/src/compiler/expression/abort.rs b/src/compiler/expression/abort.rs index 5ed3de3a0f..2b2aeca628 100644 --- a/src/compiler/expression/abort.rs +++ b/src/compiler/expression/abort.rs @@ -65,7 +65,10 @@ impl Expression for Abort { } fn type_info(&self, state: &TypeState) -> TypeInfo { - TypeInfo::new(state, TypeDef::never()) + let returns = self.message.as_ref().map_or(Kind::never(), |message| { + message.type_info(state).result.returns().to_owned() + }); + TypeInfo::new(state, TypeDef::never().with_returns(returns)) } } diff --git a/src/compiler/expression/array.rs b/src/compiler/expression/array.rs index 295318e7c6..acaf4ff409 100644 --- a/src/compiler/expression/array.rs +++ b/src/compiler/expression/array.rs @@ -1,11 +1,14 @@ use std::{collections::BTreeMap, fmt, ops::Deref}; -use crate::compiler::{ - expression::{Expr, Resolved}, - state::{TypeInfo, TypeState}, - Context, Expression, TypeDef, -}; use crate::value::Value; +use crate::{ + compiler::{ + expression::{Expr, Resolved}, + state::{TypeInfo, TypeState}, + Context, Expression, TypeDef, + }, + value::Kind, +}; #[derive(Debug, Clone, PartialEq)] pub struct Array { @@ -62,13 +65,22 @@ impl Expression for Array { type_defs.push(type_def); } + let returns = type_defs.iter().fold(Kind::never(), |returns, type_def| { + returns.union(type_def.returns().clone()) + }); + let collection = type_defs .into_iter() .enumerate() .map(|(index, type_def)| (index.into(), type_def.into())) .collect::>(); - TypeInfo::new(state, TypeDef::array(collection).maybe_fallible(fallible)) + TypeInfo::new( + state, + TypeDef::array(collection) + .maybe_fallible(fallible) + .with_returns(returns), + ) } } diff --git a/src/compiler/expression/block.rs b/src/compiler/expression/block.rs index 90aeaace5e..a947e081b0 100644 --- a/src/compiler/expression/block.rs +++ b/src/compiler/expression/block.rs @@ -5,6 +5,7 @@ use crate::compiler::{ expression::{Expr, Resolved}, Context, Expression, TypeDef, }; +use crate::value::Kind; #[derive(Debug, Clone, PartialEq)] pub struct Block { @@ -61,23 +62,26 @@ impl Expression for Block { let mut state = state.clone(); let mut result = TypeDef::null(); let mut fallible = false; + let mut returns = Kind::never(); + let mut after_never_expression = false; for expr in &self.inner { result = expr.apply_type_info(&mut state); - if result.is_fallible() { + if !after_never_expression && result.is_fallible() { fallible = true; } if result.is_never() { - break; + after_never_expression = true; } + returns.merge_keep(result.returns().clone(), false); } if self.new_scope { state.local = parent_locals.apply_child_scope(state.local); } - TypeInfo::new(state, result.maybe_fallible(fallible)) + TypeInfo::new(state, result.maybe_fallible(fallible).with_returns(returns)) } } diff --git a/src/compiler/expression/function_call.rs b/src/compiler/expression/function_call.rs index 0ffa74707f..c5bb037498 100644 --- a/src/compiler/expression/function_call.rs +++ b/src/compiler/expression/function_call.rs @@ -520,10 +520,14 @@ impl<'a> Builder<'a> { // Check the type definition of the resulting block.This needs to match // whatever is configured by the closure input type. let expected_kind = input.output.into_kind(); - if expected_kind.is_superset(block_type_def.kind()).is_err() { + let found_kind = block_type_def + .kind() + .union(block_type_def.returns().clone()); + + if expected_kind.is_superset(&found_kind).is_err() { return Err(FunctionCallError::ReturnTypeMismatch { block_span, - found_kind: block_type_def.kind().clone(), + found_kind, expected_kind, }); } @@ -643,6 +647,14 @@ impl Expression for FunctionCall { // propagate the error err } + ExpressionError::Return { span, .. } => ExpressionError::Error { + message: "return cannot be used inside closures".to_owned(), + labels: vec![Label::primary( + "return cannot be used inside closures", + span, + )], + notes: Vec::new(), + }, ExpressionError::Error { message, mut labels, diff --git a/src/compiler/expression/if_statement.rs b/src/compiler/expression/if_statement.rs index f5ec072e41..5d2c147f57 100644 --- a/src/compiler/expression/if_statement.rs +++ b/src/compiler/expression/if_statement.rs @@ -31,7 +31,7 @@ impl Expression for IfStatement { fn type_info(&self, state: &TypeState) -> TypeInfo { let mut state = state.clone(); - self.predicate.apply_type_info(&mut state); + let predicate_info = self.predicate.apply_type_info(&mut state); let if_info = self.if_block.type_info(&state); @@ -42,7 +42,12 @@ impl Expression for IfStatement { let final_state = if_info.state.merge(else_info.state); // result is from either "if" or the "else" block - let result = if_info.result.union(else_info.result); + let mut result = if_info.result.union(else_info.result); + + // predicate can also return + result + .returns_mut() + .merge_keep(predicate_info.returns().clone(), false); TypeInfo::new(final_state, result) } else { @@ -50,7 +55,12 @@ impl Expression for IfStatement { let final_state = if_info.state.merge(state); // if the predicate is false, "null" is returned. - let result = if_info.result.or_null(); + let mut result = if_info.result.or_null(); + + // predicate can also return + result + .returns_mut() + .merge_keep(predicate_info.returns().clone(), false); TypeInfo::new(final_state, result) } diff --git a/src/compiler/expression/not.rs b/src/compiler/expression/not.rs index 1c2f475adf..ff024ab1cd 100644 --- a/src/compiler/expression/not.rs +++ b/src/compiler/expression/not.rs @@ -45,7 +45,9 @@ impl Expression for Not { let result = self.inner.apply_type_info(&mut state); TypeInfo::new( state, - TypeDef::boolean().maybe_fallible(result.is_fallible()), + TypeDef::boolean() + .maybe_fallible(result.is_fallible()) + .with_returns(result.returns().clone()), ) } } diff --git a/src/compiler/expression/object.rs b/src/compiler/expression/object.rs index b7c78a3a02..022f6fac2d 100644 --- a/src/compiler/expression/object.rs +++ b/src/compiler/expression/object.rs @@ -1,11 +1,14 @@ use std::{collections::BTreeMap, fmt, ops::Deref}; -use crate::compiler::{ - expression::{Expr, Resolved}, - state::{TypeInfo, TypeState}, - Context, Expression, TypeDef, -}; use crate::value::{KeyString, Value}; +use crate::{ + compiler::{ + expression::{Expr, Resolved}, + state::{TypeInfo, TypeState}, + Context, Expression, TypeDef, + }, + value::Kind, +}; #[derive(Debug, Clone, PartialEq)] pub struct Object { @@ -47,17 +50,24 @@ impl Expression for Object { fn type_info(&self, state: &TypeState) -> TypeInfo { let mut state = state.clone(); let mut fallible = false; + let mut returns = Kind::never(); let mut type_defs = BTreeMap::new(); for (k, expr) in &self.inner { let type_def = expr.apply_type_info(&mut state).upgrade_undefined(); + returns.merge_keep(type_def.returns().clone(), false); // If any expression is fallible, the entire object is fallible. fallible |= type_def.is_fallible(); // If any expression aborts, the entire object aborts if type_def.is_never() { - return TypeInfo::new(state, TypeDef::never().maybe_fallible(fallible)); + return TypeInfo::new( + state, + TypeDef::never() + .maybe_fallible(fallible) + .with_returns(returns), + ); } type_defs.insert(k.clone(), type_def); } @@ -67,7 +77,9 @@ impl Expression for Object { .map(|(field, type_def)| (field.into(), type_def.into())) .collect::>(); - let result = TypeDef::object(collection).maybe_fallible(fallible); + let result = TypeDef::object(collection) + .maybe_fallible(fallible) + .with_returns(returns); TypeInfo::new(state, result) } } diff --git a/src/compiler/expression/return.rs b/src/compiler/expression/return.rs new file mode 100644 index 0000000000..67a5027fe9 --- /dev/null +++ b/src/compiler/expression/return.rs @@ -0,0 +1,119 @@ +use std::fmt; + +use crate::compiler::{ + expression::Resolved, + state::{TypeInfo, TypeState}, + Context, Expression, Span, TypeDef, +}; +use crate::diagnostic::{DiagnosticMessage, Label, Note}; +use crate::parser::ast::Node; + +use super::{Expr, ExpressionError}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Return { + span: Span, + expr: Box, +} + +impl Return { + /// # Errors + /// + /// * The returned value must not be fallible + pub fn new(span: Span, expr: Node, state: &TypeState) -> Result { + let (expr_span, expr) = expr.take(); + let type_def = expr.type_info(state).result; + + if type_def.is_fallible() { + return Err(Error { + variant: ErrorVariant::FallibleExpr, + expr_span, + }); + } + + Ok(Self { + span, + expr: Box::new(expr), + }) + } +} + +impl Expression for Return { + fn resolve(&self, ctx: &mut Context) -> Resolved { + Err(ExpressionError::Return { + span: self.span, + value: self.expr.resolve(ctx)?, + }) + } + + fn type_info(&self, state: &TypeState) -> TypeInfo { + let value = self.expr.type_info(state); + TypeInfo::new( + state, + TypeDef::never().with_returns(value.result.kind().clone()), + ) + } +} + +impl fmt::Display for Return { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "return") + } +} + +// ----------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct Error { + variant: ErrorVariant, + expr_span: Span, +} + +#[derive(thiserror::Error, Debug)] +pub(crate) enum ErrorVariant { + #[error("unhandled fallible expression")] + FallibleExpr, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:#}", self.variant) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.variant) + } +} + +impl DiagnosticMessage for Error { + fn code(&self) -> usize { + use ErrorVariant::FallibleExpr; + + match self.variant { + FallibleExpr => 631, + } + } + + fn labels(&self) -> Vec