diff --git a/compiler-core/src/ast/typed.rs b/compiler-core/src/ast/typed.rs index c3daced0cb5..eb148b6b52d 100644 --- a/compiler-core/src/ast/typed.rs +++ b/compiler-core/src/ast/typed.rs @@ -126,6 +126,12 @@ pub enum TypedExpr { type_: Arc, }, + Echo { + location: SrcSpan, + type_: Arc, + expression: Option>, + }, + BitArray { location: SrcSpan, type_: Arc, @@ -182,6 +188,11 @@ impl TypedExpr { | Self::ModuleSelect { .. } | Self::Invalid { .. } => self.self_if_contains_location(byte_index), + Self::Echo { expression, .. } => expression + .as_ref() + .and_then(|e| e.find_node(byte_index)) + .or_else(|| self.self_if_contains_location(byte_index)), + Self::Todo { kind, .. } => match kind { TodoKind::Keyword => self.self_if_contains_location(byte_index), // We don't want to match on todos that were implicitly inserted @@ -316,6 +327,7 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Var { location, .. } | Self::Todo { location, .. } + | Self::Echo { location, .. } | Self::Case { location, .. } | Self::Call { location, .. } | Self::List { location, .. } @@ -343,6 +355,7 @@ impl TypedExpr { | Self::Int { location, .. } | Self::Var { location, .. } | Self::Todo { location, .. } + | Self::Echo { location, .. } | Self::Case { location, .. } | Self::Call { location, .. } | Self::List { location, .. } @@ -372,6 +385,7 @@ impl TypedExpr { | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::Todo { .. } + | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BinOp { .. } | TypedExpr::Float { .. } @@ -413,6 +427,7 @@ impl TypedExpr { Self::Fn { type_, .. } | Self::Int { type_, .. } | Self::Todo { type_, .. } + | Self::Echo { type_, .. } | Self::Case { type_, .. } | Self::List { type_, .. } | Self::Call { type_, .. } @@ -473,6 +488,7 @@ impl TypedExpr { | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } + | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } @@ -549,6 +565,13 @@ impl TypedExpr { // pure value constructor and raise a warning for those as well. TypedExpr::Block { .. } | TypedExpr::Case { .. } => false, + // `echo` just returns the thing its printing, so it's pure as long as + // the printed thing is. If it's being used as a step of a pipeline then + // we always consider it pure + TypedExpr::Echo { expression, .. } => expression + .as_ref() + .map_or(true, |e| e.is_pure_value_constructor()), + // `panic`, `todo`, and placeholders are never considered pure value constructors, // we don't want to raise a warning for an unused value if it's one // of those. diff --git a/compiler-core/src/ast/visit.rs b/compiler-core/src/ast/visit.rs index f2eff72030c..356ac0178f7 100644 --- a/compiler-core/src/ast/visit.rs +++ b/compiler-core/src/ast/visit.rs @@ -76,6 +76,15 @@ pub trait Visit<'ast> { visit_typed_expr(self, expr); } + fn visit_typed_expr_echo( + &mut self, + location: &'ast SrcSpan, + type_: &'ast Arc, + expression: &'ast Option>, + ) { + visit_typed_expr_echo(self, location, type_, expression); + } + fn visit_typed_expr_int( &mut self, location: &'ast SrcSpan, @@ -612,6 +621,11 @@ where } TypedExpr::NegateInt { location, value } => v.visit_typed_expr_negate_int(location, value), TypedExpr::Invalid { location, type_ } => v.visit_typed_expr_invalid(location, type_), + TypedExpr::Echo { + location, + expression, + type_, + } => v.visit_typed_expr_echo(location, type_, expression), } } @@ -830,6 +844,19 @@ pub fn visit_typed_expr_todo<'a, V>( } } +fn visit_typed_expr_echo<'a, V>( + v: &mut V, + _location: &'a SrcSpan, + _type_: &'a Arc, + expression: &'a Option>, +) where + V: Visit<'a> + ?Sized, +{ + if let Some(expression) = expression { + v.visit_typed_expr(expression) + } +} + pub fn visit_typed_expr_panic<'a, V>( v: &mut V, _location: &'a SrcSpan, diff --git a/compiler-core/src/erlang.rs b/compiler-core/src/erlang.rs index 73af03af79f..e2cf868e9c4 100644 --- a/compiler-core/src/erlang.rs +++ b/compiler-core/src/erlang.rs @@ -1711,6 +1711,7 @@ fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool { | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } + | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } @@ -1867,6 +1868,12 @@ fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { ), TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"), + + TypedExpr::Echo { + location: _, + expression: _, + type_: _, + } => todo!("generate code for echo"), } } diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index af082b5b0f9..fa6b89d120b 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -204,6 +204,12 @@ impl<'module> Generator<'module> { TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value), + TypedExpr::Echo { + location: _, + expression: _, + type_: _, + } => todo!("generate js code for echo"), + TypedExpr::Invalid { .. } => { panic!("invalid expressions should not reach code generation") } @@ -1636,6 +1642,8 @@ impl TypedExpr { | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } => true, + TypedExpr::Echo { .. } => todo!("understand what this means"), + TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } @@ -1711,6 +1719,8 @@ fn requires_semicolon(statement: &TypedStatement) -> bool { | TypedExpr::Block { .. }, ) => true, + Statement::Expression(TypedExpr::Echo { .. }) => todo!("understand if it is required"), + Statement::Expression( TypedExpr::Todo { .. } | TypedExpr::Case { .. } diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs index 54ab583a7e4..d64de7fceb9 100644 --- a/compiler-core/src/type_/expression.rs +++ b/compiler-core/src/type_/expression.rs @@ -476,8 +476,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { expression: Option>, ) -> Result { if let Some(expression) = expression { - let typed_expression = self.infer(*expression); - Ok(todo!("typed expr ECHO")) + let expression = self.infer(*expression)?; + Ok(TypedExpr::Echo { + location, + type_: expression.type_(), + expression: Some(Box::new(expression)), + }) } else { Err(Error::EchoWithNoFollowingExpression { location }) } diff --git a/compiler-core/src/type_/pipe.rs b/compiler-core/src/type_/pipe.rs index 2de5046ac1f..da1446afd32 100644 --- a/compiler-core/src/type_/pipe.rs +++ b/compiler-core/src/type_/pipe.rs @@ -127,7 +127,19 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { location, expression: None, } => { - todo!("This echo is actually ok!") + let var = self.expr_typer.new_unbound_var(); + let identity_function = Type::Fn { + args: vec![var.clone()], + retrn: var, + }; + // An echo that is not followed by an expression that is + // used as a pipeline's step is just like the identity + // function. + TypedExpr::Echo { + location, + expression: None, + type_: Arc::new(identity_function), + } } // right(left)