Forbid let-binding as last expression of a sequence

Fixes #283
This commit is contained in:
KtorZ 2023-01-19 18:05:57 +01:00
parent c5e876e817
commit 10fb7455f3
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 59 additions and 13 deletions

View File

@ -647,7 +647,7 @@ impl<'comments> Formatter<'comments> {
}
}
fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
pub fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
let comments = self.pop_comments(expr.start_byte_index());
let document = match expr {

View File

@ -1,6 +1,7 @@
use super::Type;
use crate::{
ast::{Annotation, BinOp, CallArg, Span, TodoKind, UntypedPattern},
expr,
format::Formatter,
levenshtein,
pretty::Documentable,
@ -53,6 +54,12 @@ pub enum Error {
#[error("I found a discarded expression not bound to a variable.")]
ImplicitlyDiscardedExpression { location: Span },
#[error("I discovered a function who's ending with an assignment.")]
LastExpressionIsAssignment {
location: Span,
expr: expr::UntypedExpr,
},
#[error("I saw a {} fields in a context where there should be {}.\n", given.purple(), expected.purple())]
IncorrectFieldsArity {
location: Span,
@ -378,6 +385,7 @@ impl Diagnostic for Error {
Self::ImplicitlyDiscardedExpression { .. } => {
Some(Box::new("implicitly_discarded_expr"))
}
Self::LastExpressionIsAssignment { .. } => Some(Box::new("last_expr_is_assignment")),
Self::IncorrectFieldsArity { .. } => Some(Box::new("incorrect_fields_arity")),
Self::IncorrectFunctionCallArity { .. } => Some(Box::new("incorrect_fn_arity")),
Self::IncorrectPatternArity { .. } => Some(Box::new("incorrect_pattern_arity")),
@ -495,11 +503,29 @@ impl Diagnostic for Error {
Self::FunctionTypeInData { .. } => Some(Box::new("Data types can't have functions in them due to how Plutus Data works.")),
Self::ImplicitlyDiscardedExpression { .. } => Some(Box::new(formatdoc! {
r#"A function can contain a sequence of expressions. However, any expression but the last one must be assign to a variable using the {let_keyword} keyword. If you really wish to discard an expression that is unused, you can prefix its name with '{discard}'.
r#"A function can contain a sequence of expressions. However, any expression but the last one must be assign to a variable using the {keyword_let} keyword. If you really wish to discard an expression that is unused, you can assign it to '{discard}'.
"#
, let_keyword = "let".yellow()
, keyword_let = "let".yellow()
, discard = "_".yellow()
})),
Self::LastExpressionIsAssignment { expr, .. } => Some(Box::new(formatdoc! {
r#"In Aiken, functions must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
If you really meant to return that last expression, try to replace it with the following:
{sample}
"#
, sample = Formatter::new()
.expr(expr)
.to_pretty_string(70)
.lines()
.enumerate()
.map(|(ix, line)| if ix == 0 { format!("╰─▶ {}", line.yellow()) } else { format!(" {line}").yellow().to_string() })
.collect::<Vec<_>>()
.join("\n")
})),
Self::IncorrectFieldsArity { .. } => None,
Self::IncorrectFunctionCallArity { expected, .. } => Some(Box::new(formatdoc! {
@ -1161,10 +1187,12 @@ impl Diagnostic for Error {
Self::FunctionTypeInData { location } => Some(Box::new(
vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
)),
Self::ImplicitlyDiscardedExpression { location, .. } => Some(Box::new(
vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
)),
Self::LastExpressionIsAssignment { location, .. } => Some(Box::new(
vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
)),
Self::IncorrectFieldsArity { location, .. } => Some(Box::new(
vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
)),
@ -1299,6 +1327,9 @@ impl Diagnostic for Error {
Self::DuplicateTypeName { .. } => None,
Self::FunctionTypeInData { .. } => None,
Self::ImplicitlyDiscardedExpression { .. } => None,
Self::LastExpressionIsAssignment { .. } => Some(Box::new(
"https://aiken-lang.org/language-tour/functions#named-functions"
)),
Self::IncorrectFieldsArity { .. } => Some(Box::new(
"https://aiken-lang.org/language-tour/custom-types",
)),

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, sync::Arc};
use std::{cmp::Ordering, collections::HashMap, sync::Arc};
use vec1::Vec1;
@ -191,8 +191,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.field_map())
}
fn assert_assignment(&self, expr: &TypedExpr) -> Result<(), Error> {
if !matches!(*expr, TypedExpr::Assignment { .. }) {
fn assert_assignment(&self, expr: &UntypedExpr) -> Result<(), Error> {
if !matches!(*expr, UntypedExpr::Assignment { .. }) {
return Err(Error::ImplicitlyDiscardedExpression {
location: expr.location(),
});
@ -200,6 +200,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(())
}
fn assert_no_assignment(&self, expr: &UntypedExpr) -> Result<(), Error> {
match expr {
UntypedExpr::Assignment { value, .. } => Err(Error::LastExpressionIsAssignment {
location: expr.location(),
expr: *value.clone(),
}),
_ => Ok(()),
}
}
pub fn in_new_scope<T>(&mut self, process_scope: impl FnOnce(&mut Self) -> T) -> T {
// Create new scope
let environment_reset_data = self.environment.open_new_scope();
@ -1671,15 +1681,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let mut expressions = Vec::with_capacity(count);
for (i, expression) in untyped.into_iter().enumerate() {
let expression = self.infer(expression)?;
match i.cmp(&(count - 1)) {
// When the expression is the last in a sequence, we enforce it is NOT
// an assignment (kind of treat assignments like statements).
Ordering::Equal => self.assert_no_assignment(&expression)?,
// This isn't the final expression in the sequence, so it *must*
// be a let-binding; we do not allow anything else.
if i < count - 1 {
self.assert_assignment(&expression)?;
Ordering::Less => self.assert_assignment(&expression)?,
// Can't actually happen
Ordering::Greater => (),
}
expressions.push(expression);
expressions.push(self.infer(expression)?);
}
Ok(TypedExpr::Sequence {