commit
						97c382715f
					
				|  | @ -1,5 +1,13 @@ | |||
| # Changelog | ||||
| 
 | ||||
| ## v1.1.14 - UNRELEASED | ||||
| 
 | ||||
| ### Changed | ||||
| 
 | ||||
| - **aiken-lang**: Prevent (type error) backpassing blocks with empty continuation. See [#1111](https://github.com/aiken-lang/aiken/issues/1111). @KtorZ | ||||
| - **aiken-lang**: Change default placeholder for `trace` to `Void` instead of `todo`. @KtorZ | ||||
| - **aiken-lang**: Disallow (parse error) dangling colon `:` in traces. See [#1113](https://github.com/aiken-lang/aiken/issues/1113). @KtorZ | ||||
| 
 | ||||
| ## v1.1.13 - 2025-02-26 | ||||
| 
 | ||||
| ### Added | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ use crate::{ | |||
|     parser::token::Token, | ||||
| }; | ||||
| use indoc::formatdoc; | ||||
| use itertools::Itertools; | ||||
| use miette::Diagnostic; | ||||
| use owo_colors::{OwoColorize, Stream::Stdout}; | ||||
| use std::collections::HashSet; | ||||
|  | @ -18,6 +19,7 @@ use std::collections::HashSet; | |||
|                     "I am looking for one of the following patterns:\n{}", | ||||
|                     expected | ||||
|                         .iter() | ||||
|                         .sorted() | ||||
|                         .map(|x| format!( | ||||
|                             "→ {}", | ||||
|                             x.to_aiken() | ||||
|  | @ -320,7 +322,7 @@ fn fmt_unknown_curve(curve: &String, point: &Option<String>) -> String { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Diagnostic, thiserror::Error)] | ||||
| pub enum Pattern { | ||||
|     #[error("I found an unexpected char '{0:?}'.")] | ||||
|     #[diagnostic(help("Try removing it!"))] | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| use crate::{ | ||||
|     ast::TraceKind, | ||||
|     ast::{well_known, TraceKind}, | ||||
|     expr::UntypedExpr, | ||||
|     parser::{ | ||||
|         error::{ParseError, Pattern}, | ||||
|  | @ -32,7 +32,8 @@ pub fn parser<'a>( | |||
|                 choice((just(Token::Colon), just(Token::Comma))) | ||||
|                     .then( | ||||
|                         choice((string::hybrid(), expression.clone())) | ||||
|                             .separated_by(just(Token::Comma)), | ||||
|                             .separated_by(just(Token::Comma)) | ||||
|                             .at_least(1), | ||||
|                     ) | ||||
|                     .validate(|(token, arguments), span, emit| { | ||||
|                         if token != Token::Colon { | ||||
|  | @ -53,7 +54,10 @@ pub fn parser<'a>( | |||
|                 |((label, arguments), continuation), span| UntypedExpr::Trace { | ||||
|                     kind: TraceKind::Trace, | ||||
|                     location: span, | ||||
|                     then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::todo(None, span))), | ||||
|                     then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::Var { | ||||
|                         location: span, | ||||
|                         name: well_known::VOID.to_string(), | ||||
|                     })), | ||||
|                     label: Box::new(label), | ||||
|                     arguments, | ||||
|                 }, | ||||
|  | @ -193,4 +197,15 @@ mod tests { | |||
|             "#
 | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn trace_dangling_colons() { | ||||
|         assert_expr!( | ||||
|             r#" | ||||
|             let debug = fn() { | ||||
|               trace "foo": | ||||
|             } | ||||
|             "#
 | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,14 +2,4 @@ | |||
| source: crates/aiken-lang/src/parser/expr/assignment.rs | ||||
| description: "Invalid code (parse error):\n\nlet a =\n// foo\nlet b = 42\n" | ||||
| --- | ||||
| [ | ||||
|     ParseError { | ||||
|         kind: UnfinishedAssignmentRightHandSide, | ||||
|         span: 0..25, | ||||
|         while_parsing: None, | ||||
|         expected: {}, | ||||
|         label: Some( | ||||
|             "invalid assignment right-hand side", | ||||
|         ), | ||||
|     }, | ||||
| ] | ||||
| I spotted an unfinished assignment. | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs | ||||
| description: "Invalid code (parse error):\n\nlet debug = fn() {\n  trace \"foo\":\n}\n" | ||||
| --- | ||||
| I found an unexpected token '}'. | ||||
|  | @ -5,17 +5,9 @@ description: "Code:\n\ntrace some_var\n" | |||
| Trace { | ||||
|     kind: Trace, | ||||
|     location: 0..14, | ||||
|     then: Trace { | ||||
|         kind: Todo, | ||||
|     then: Var { | ||||
|         location: 0..14, | ||||
|         then: ErrorTerm { | ||||
|             location: 0..14, | ||||
|         }, | ||||
|         label: String { | ||||
|             location: 0..14, | ||||
|             value: "aiken::todo", | ||||
|         }, | ||||
|         arguments: [], | ||||
|         name: "Void", | ||||
|     }, | ||||
|     label: Var { | ||||
|         location: 6..14, | ||||
|  |  | |||
|  | @ -5,17 +5,9 @@ description: "Code:\n\ntrace foo: \"bar\"\n" | |||
| Trace { | ||||
|     kind: Trace, | ||||
|     location: 0..16, | ||||
|     then: Trace { | ||||
|         kind: Todo, | ||||
|     then: Var { | ||||
|         location: 0..16, | ||||
|         then: ErrorTerm { | ||||
|             location: 0..16, | ||||
|         }, | ||||
|         label: String { | ||||
|             location: 0..16, | ||||
|             value: "aiken::todo", | ||||
|         }, | ||||
|         arguments: [], | ||||
|         name: "Void", | ||||
|     }, | ||||
|     label: Var { | ||||
|         location: 6..9, | ||||
|  |  | |||
|  | @ -5,17 +5,9 @@ description: "Code:\n\ntrace \"foo\": @\"bar\", baz\n" | |||
| Trace { | ||||
|     kind: Trace, | ||||
|     location: 0..24, | ||||
|     then: Trace { | ||||
|         kind: Todo, | ||||
|     then: Var { | ||||
|         location: 0..24, | ||||
|         then: ErrorTerm { | ||||
|             location: 0..24, | ||||
|         }, | ||||
|         label: String { | ||||
|             location: 0..24, | ||||
|             value: "aiken::todo", | ||||
|         }, | ||||
|         arguments: [], | ||||
|         name: "Void", | ||||
|     }, | ||||
|     label: String { | ||||
|         location: 6..11, | ||||
|  |  | |||
|  | @ -2,14 +2,4 @@ | |||
| source: crates/aiken-lang/src/parser/expr/when/mod.rs | ||||
| description: "Invalid code (parse error):\n\nwhen a is {\n  2 if x > 1 -> 3\n  _ -> 1\n}\n" | ||||
| --- | ||||
| [ | ||||
|     ParseError { | ||||
|         kind: DeprecatedWhenClause, | ||||
|         span: 14..29, | ||||
|         while_parsing: None, | ||||
|         expected: {}, | ||||
|         label: Some( | ||||
|             "deprecated", | ||||
|         ), | ||||
|     }, | ||||
| ] | ||||
| I found a now-deprecated clause guard in a when/is expression. | ||||
|  |  | |||
|  | @ -2,14 +2,4 @@ | |||
| source: crates/aiken-lang/src/parser/pattern/bytearray.rs | ||||
| description: "Invalid code (parse error):\n\nwhen foo is {\n    #<Bls12_381, G1>\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\" -> False\n    _ -> True\n}\n" | ||||
| --- | ||||
| [ | ||||
|     ParseError { | ||||
|         kind: PatternMatchOnCurvePoint, | ||||
|         span: 18..132, | ||||
|         while_parsing: None, | ||||
|         expected: {}, | ||||
|         label: Some( | ||||
|             "cannot pattern-match on curve point", | ||||
|         ), | ||||
|     }, | ||||
| ] | ||||
| I choked on a curve point in a bytearray pattern. | ||||
|  |  | |||
|  | @ -2,14 +2,4 @@ | |||
| source: crates/aiken-lang/src/parser/pattern/bytearray.rs | ||||
| description: "Invalid code (parse error):\n\nwhen foo is {\n    #<Bls12_381, G2>\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\" -> False\n    _ -> True\n}\n" | ||||
| --- | ||||
| [ | ||||
|     ParseError { | ||||
|         kind: PatternMatchOnCurvePoint, | ||||
|         span: 18..228, | ||||
|         while_parsing: None, | ||||
|         expected: {}, | ||||
|         label: Some( | ||||
|             "cannot pattern-match on curve point", | ||||
|         ), | ||||
|     }, | ||||
| ] | ||||
| I choked on a curve point in a bytearray pattern. | ||||
|  |  | |||
|  | @ -2,14 +2,4 @@ | |||
| source: crates/aiken-lang/src/parser/pattern/string.rs | ||||
| description: "Invalid code (parse error):\n\nwhen foo is {\n  @\"foo\" -> True\n}\n" | ||||
| --- | ||||
| [ | ||||
|     ParseError { | ||||
|         kind: PatternMatchOnString, | ||||
|         span: 16..22, | ||||
|         while_parsing: None, | ||||
|         expected: {}, | ||||
|         label: Some( | ||||
|             "cannot pattern-match on string", | ||||
|         ), | ||||
|     }, | ||||
| ] | ||||
| I refuse to cooperate and match a utf-8 string. | ||||
|  |  | |||
|  | @ -1,12 +1,14 @@ | |||
| use std::fmt; | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)] | ||||
| #[derive(
 | ||||
|     Clone, Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Copy, serde::Serialize, serde::Deserialize, | ||||
| )] | ||||
| pub enum Base { | ||||
|     Decimal { numeric_underscore: bool }, | ||||
|     Hexadecimal, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, PartialEq, Hash, Eq)] | ||||
| #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Hash, Eq)] | ||||
| pub enum Token { | ||||
|     Error(char), | ||||
|     Name { name: String }, | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ macro_rules! assert_expr { | |||
|                     prepend_module_to_snapshot => false, | ||||
|                     omit_expression => true | ||||
|                 }, { | ||||
|                     insta::assert_debug_snapshot!(err); | ||||
|                     insta::assert_snapshot!(err.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join("\n")); | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ fn check_module( | |||
| 
 | ||||
|     for (package, module) in extra { | ||||
|         let mut warnings = vec![]; | ||||
| 
 | ||||
|         let typed_module = module | ||||
|             .infer( | ||||
|                 &id_gen, | ||||
|  | @ -3309,6 +3310,44 @@ fn softcasting_unused_let_binding() { | |||
|     assert!(warnings.is_empty(), "should not contain any warnings"); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn dangling_let_in_block() { | ||||
|     let source_code = r#" | ||||
|         fn for_each(xs: List<Int>, with: fn(Int) -> a) -> a { | ||||
|             todo | ||||
|         } | ||||
| 
 | ||||
|         test foo() { | ||||
|             { | ||||
|                 let _ <- for_each([1, 2, 3]) | ||||
|             } | ||||
|         } | ||||
|     "#;
 | ||||
| 
 | ||||
|     let result = check_validator(parse(source_code)); | ||||
| 
 | ||||
|     assert!( | ||||
|         matches!(result, Err((_, Error::LastExpressionIsAssignment { .. }))), | ||||
|         "{result:?}" | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn default_trace_return() { | ||||
|     let source_code = r#" | ||||
|         fn debug() { | ||||
|           trace @"patate": Void | ||||
|         } | ||||
| 
 | ||||
|         test foo() { | ||||
|             debug() | ||||
|             True | ||||
|         } | ||||
|     "#;
 | ||||
| 
 | ||||
|     assert!(matches!(check_validator(parse(source_code)), Ok(..))) | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn dangling_trace_let_standalone() { | ||||
|     let source_code = r#" | ||||
|  |  | |||
|  | @ -1970,11 +1970,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|         PipeTyper::infer(self, expressions) | ||||
|     } | ||||
| 
 | ||||
|     #[allow(clippy::result_large_err)] | ||||
|     fn backpass( | ||||
|         &mut self, | ||||
|         breakpoint: UntypedExpr, | ||||
|         mut continuation: Vec<UntypedExpr>, | ||||
|     ) -> UntypedExpr { | ||||
|     ) -> Result<UntypedExpr, Error> { | ||||
|         let UntypedExpr::Assignment { | ||||
|             location, | ||||
|             value, | ||||
|  | @ -1985,6 +1986,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|             unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"); | ||||
|         }; | ||||
| 
 | ||||
|         if continuation.is_empty() { | ||||
|             return Err(Error::LastExpressionIsAssignment { | ||||
|                 location, | ||||
|                 expr: *value, | ||||
|                 patterns: patterns.clone(), | ||||
|                 kind, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         let value_location = value.location(); | ||||
| 
 | ||||
|         let call_location = Span { | ||||
|  | @ -2101,11 +2111,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|                     value: UntypedExpr::lambda(names, continuation, lambda_span), | ||||
|                 }); | ||||
| 
 | ||||
|                 UntypedExpr::Call { | ||||
|                 Ok(UntypedExpr::Call { | ||||
|                     location: call_location, | ||||
|                     fun, | ||||
|                     arguments: new_arguments, | ||||
|                 } | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|             // This typically occurs on function captures. We do not try to assert anything on the
 | ||||
|  | @ -2136,15 +2146,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|                 }; | ||||
| 
 | ||||
|                 if arguments.is_empty() { | ||||
|                     call | ||||
|                     Ok(call) | ||||
|                 } else { | ||||
|                     UntypedExpr::Fn { | ||||
|                     Ok(UntypedExpr::Fn { | ||||
|                         location: call_location, | ||||
|                         fn_style, | ||||
|                         arguments, | ||||
|                         body: call.into(), | ||||
|                         return_annotation, | ||||
|                     } | ||||
|                     }) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -2152,7 +2162,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|             // with our continuation. If the expression isn't callable? No problem, the
 | ||||
|             // type-checker will catch that eventually in exactly the same way as if the code was
 | ||||
|             // written like that to begin with.
 | ||||
|             _ => UntypedExpr::Call { | ||||
|             _ => Ok(UntypedExpr::Call { | ||||
|                 location: call_location, | ||||
|                 fun: value, | ||||
|                 arguments: vec![CallArg { | ||||
|  | @ -2160,7 +2170,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|                     label: None, | ||||
|                     value: UntypedExpr::lambda(names, continuation, lambda_span), | ||||
|                 }], | ||||
|             }, | ||||
|             }), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -2203,7 +2213,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { | |||
|         } | ||||
| 
 | ||||
|         if let Some(breakpoint) = breakpoint { | ||||
|             prefix.push(self.backpass(breakpoint, suffix)); | ||||
|             prefix.push(self.backpass(breakpoint, suffix)?); | ||||
|             return self.infer_seq(location, prefix); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Matthias Benkort
						Matthias Benkort