Allow implicit discard when right-hand side is Void.

This is the most intuitive/expected behavior. Otherwise, it forces a pointless let-binding to 'Void' or into a discard.
This commit is contained in:
KtorZ 2024-03-08 23:48:42 +01:00
parent d73f8fd6c2
commit d6cc9bdfbe
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
6 changed files with 79 additions and 16 deletions

View File

@ -32,6 +32,7 @@
- **aiken-lang**: disallow `MLResult` in a type definition. @rvcas - **aiken-lang**: disallow `MLResult` in a type definition. @rvcas
- **aiken-lang**: reversed deserialization of bls types out of data types. @rvcas - **aiken-lang**: reversed deserialization of bls types out of data types. @rvcas
- **aiken-lang**: validator args unexpectedly unbound causing code gen crashes. @rvcas - **aiken-lang**: validator args unexpectedly unbound causing code gen crashes. @rvcas
- **aiken-lang**: allow implicitly discarded values when right-hand-side unified with `Void`. @KtorZ
### Changed ### Changed

View File

@ -93,6 +93,19 @@ fn validator_illegal_return_type() {
)) ))
} }
#[test]
fn implicitly_discard_void() {
let source_code = r#"
pub fn label(str: String) -> Void {
trace str Void
}
"#;
let (warnings, _) = check_validator(parse(source_code)).expect("should type-check");
assert!(warnings.is_empty(), "no warnings: {warnings:#?}");
}
#[test] #[test]
fn validator_illegal_arity() { fn validator_illegal_arity() {
let source_code = r#" let source_code = r#"

View File

@ -9,16 +9,19 @@ use super::{
use crate::{ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference,
CallArg, ClauseGuard, Constant, Curve, IfBranch, LogicalOpChainKind, RecordUpdateSpread, CallArg, ClauseGuard, Constant, Curve, IfBranch, LogicalOpChainKind, Pattern,
Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg, TypedClause, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg,
TypedClauseGuard, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp,
UntypedClause, UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedIfBranch, UntypedPattern,
UntypedRecordUpdateArg,
},
builtins::{
bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void,
}, },
builtins::{bool, byte_array, function, g1_element, g2_element, int, list, string, tuple},
expr::{FnStyle, TypedExpr, UntypedExpr}, expr::{FnStyle, TypedExpr, UntypedExpr},
format, format,
line_numbers::LineNumbers, line_numbers::LineNumbers,
tipo::fields::FieldMap, tipo::{fields::FieldMap, PatternConstructor},
}; };
use std::{cmp::Ordering, collections::HashMap, rc::Rc}; use std::{cmp::Ordering, collections::HashMap, rc::Rc};
use vec1::Vec1; use vec1::Vec1;
@ -1699,20 +1702,25 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let mut expressions = Vec::with_capacity(count); let mut expressions = Vec::with_capacity(count);
for (i, expression) in untyped.into_iter().enumerate() { for (i, expression) in untyped.into_iter().enumerate() {
match i.cmp(&(count - 1)) { let no_assignment = assert_no_assignment(&expression);
let typed_expression = scope.infer(expression)?;
expressions.push(match i.cmp(&(count - 1)) {
// When the expression is the last in a sequence, we enforce it is NOT // When the expression is the last in a sequence, we enforce it is NOT
// an assignment (kind of treat assignments like statements). // an assignment (kind of treat assignments like statements).
Ordering::Equal => assert_no_assignment(&expression)?, Ordering::Equal => {
no_assignment?;
typed_expression
}
// This isn't the final expression in the sequence, so it *must* // This isn't the final expression in the sequence, so it *must*
// be a let-binding; we do not allow anything else. // be a let-binding; we do not allow anything else.
Ordering::Less => assert_assignment(&expression)?, Ordering::Less => assert_assignment(typed_expression)?,
// Can't actually happen // Can't actually happen
Ordering::Greater => (), Ordering::Greater => typed_expression,
} })
expressions.push(scope.infer(expression)?);
} }
Ok(expressions) Ok(expressions)
@ -2076,12 +2084,35 @@ fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> {
| UntypedExpr::CurvePoint { .. } => Ok(()), | UntypedExpr::CurvePoint { .. } => Ok(()),
} }
} }
fn assert_assignment(expr: &UntypedExpr) -> Result<(), Error> {
if !matches!(*expr, UntypedExpr::Assignment { .. }) { fn assert_assignment(expr: TypedExpr) -> Result<TypedExpr, Error> {
if !matches!(expr, TypedExpr::Assignment { .. }) {
if expr.tipo().is_void() {
return Ok(TypedExpr::Assignment {
location: expr.location(),
tipo: void(),
value: expr.clone().into(),
pattern: Pattern::Constructor {
is_record: false,
location: expr.location(),
name: "Void".to_string(),
constructor: PatternConstructor::Record {
name: "Void".to_string(),
field_map: None,
},
arguments: vec![],
module: None,
with_spread: false,
tipo: void(),
},
kind: AssignmentKind::Let,
});
}
return Err(Error::ImplicitlyDiscardedExpression { return Err(Error::ImplicitlyDiscardedExpression {
location: expr.location(), location: expr.location(),
}); });
} }
Ok(()) Ok(expr)
} }

View File

@ -0,0 +1,7 @@
# This file was generated by Aiken
# You typically do not need to edit this file
requirements = []
packages = []
[etags]

View File

@ -0,0 +1,3 @@
name = "aiken-lang/acceptance_test_098"
version = "0.0.0"
description = ""

View File

@ -0,0 +1,8 @@
fn label(str: String) -> Void {
trace str Void
}
test foo() {
label(@"Foo")
True
}