Skip to content

Commit

Permalink
Remove () expressions and () type except returns.
Browse files Browse the repository at this point in the history
() serves no purpose beyond indicating the absence of return values, at
least until/unless we have some kind of type polymorphism.
  • Loading branch information
mikebenfield committed Jan 7, 2025
1 parent 71ce020 commit f636bed
Show file tree
Hide file tree
Showing 30 changed files with 192 additions and 292 deletions.
6 changes: 5 additions & 1 deletion compiler/ast/src/statement/return_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ pub struct ReturnStatement {

impl fmt::Display for ReturnStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "return {}", self.expression)
if let Expression::Unit(..) = self.expression {
write!(f, "return")
} else {
write!(f, "return {}", self.expression)
}
}
}

Expand Down
24 changes: 13 additions & 11 deletions compiler/parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,18 +608,20 @@ impl<N: Network> ParserContext<'_, N> {

let (mut elements, trailing, span) = self.parse_expr_tuple()?;

match elements.len() {
// If the tuple expression is empty, return a `UnitExpression`.
0 => Ok(Expression::Unit(UnitExpression { span, id: self.node_builder.next_id() })),
1 => match trailing {
match (elements.len(), trailing) {
(0, _) | (1, true) => {
// A tuple with 0 or 1 elements - emit an error since tuples must have at least two elements.
Err(ParserError::tuple_must_have_at_least_two_elements("expression", span).into())
}
(1, false) => {
// If there is one element in the tuple but no trailing comma, e.g `(foo)`, return the element.
false => Ok(elements.swap_remove(0)),
// If there is one element in the tuple and a trailing comma, e.g `(foo,)`, emit an error since tuples must have at least two elements.
true => Err(ParserError::tuple_must_have_at_least_two_elements("expression", span).into()),
},
// Otherwise, return a tuple expression.
// Note: This is the only place where `TupleExpression` is constructed in the parser.
_ => Ok(Expression::Tuple(TupleExpression { elements, span, id: self.node_builder.next_id() })),
Ok(elements.remove(0))
}
_ => {
// Otherwise, return a tuple expression.
// Note: This is the only place where `TupleExpression` is constructed in the parser.
Ok(Expression::Tuple(TupleExpression { elements, span, id: self.node_builder.next_id() }))
}
}
}

Expand Down
91 changes: 44 additions & 47 deletions compiler/passes/src/code_generation/visit_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,59 +80,56 @@ impl<'a> CodeGenerator<'a> {
}

fn visit_return(&mut self, input: &'a ReturnStatement) -> String {
let outputs = match input.expression {
if let Expression::Unit(..) = input.expression {
// Skip empty return statements.
Expression::Unit(_) => String::new(),
_ => {
let (operand, mut expression_instructions) = self.visit_expression(&input.expression);
// Get the output type of the function.
let output = self.current_function.unwrap().output.iter();
// If the operand string is empty, initialize an empty vector.
let operand_strings = match operand.is_empty() {
true => vec![],
false => operand.split(' ').collect_vec(),
};
return String::new();
}

let mut future_output = String::new();
let mut instructions = operand_strings
.iter()
.zip_eq(output)
.map(|(operand, output)| {
// Transitions outputs with no mode are private.
// Note that this unwrap is safe because we set the variant before traversing the function.
let visibility = match (self.variant.unwrap().is_transition(), output.mode) {
(true, Mode::None) => Mode::Private,
(_, mode) => mode,
};

if let Type::Future(_) = output.type_ {
future_output = format!(
" output {} as {}.aleo/{}.future;\n",
operand,
self.program_id.unwrap().name,
self.current_function.unwrap().identifier,
);
String::new()
} else {
format!(
" output {} as {};\n",
operand,
self.visit_type_with_visibility(&output.type_, visibility)
)
}
})
.join("");
let (operand, mut expression_instructions) = self.visit_expression(&input.expression);
// Get the output type of the function.
let output = self.current_function.unwrap().output.iter();
// If the operand string is empty, initialize an empty vector.
let operand_strings = match operand.is_empty() {
true => vec![],
false => operand.split(' ').collect_vec(),
};

let mut future_output = String::new();
let mut instructions = operand_strings
.iter()
.zip_eq(output)
.map(|(operand, output)| {
// Transitions outputs with no mode are private.
// Note that this unwrap is safe because we set the variant before traversing the function.
let visibility = match (self.variant.unwrap().is_transition(), output.mode) {
(true, Mode::None) => Mode::Private,
(_, mode) => mode,
};

// Insert future output at the end.
instructions.push_str(&future_output);
if let Type::Future(_) = output.type_ {
future_output = format!(
" output {} as {}.aleo/{}.future;\n",
operand,
self.program_id.unwrap().name,
self.current_function.unwrap().identifier,
);
String::new()
} else {
format!(
" output {} as {};\n",
operand,
self.visit_type_with_visibility(&output.type_, visibility)
)
}
})
.join("");

expression_instructions.push_str(&instructions);
// Insert future output at the end.
instructions.push_str(&future_output);

expression_instructions
}
};
expression_instructions.push_str(&instructions);

outputs
expression_instructions
}

fn visit_definition(&mut self, _input: &'a DefinitionStatement) -> String {
Expand Down
9 changes: 2 additions & 7 deletions compiler/passes/src/type_checking/check_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,12 +996,7 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> {
ty
}

fn visit_unit(&mut self, input: &'a UnitExpression, additional: &Self::AdditionalInput) -> Self::Output {
// Unit expression are only allowed inside a return statement.
if !self.scope_state.is_return {
self.emit_err(TypeCheckerError::unit_expression_only_in_return_statements(input.span()));
}
self.maybe_assert_type(&Type::Unit, additional, input.span());
Type::Unit
fn visit_unit(&mut self, _input: &'a UnitExpression, _additional: &Self::AdditionalInput) -> Self::Output {
unreachable!("Unit expressions should not exist in normal code");
}
}
22 changes: 6 additions & 16 deletions compiler/passes/src/type_checking/check_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,6 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> {

// Check that the type of the definition is not a unit type, singleton tuple type, or nested tuple type.
match &input.type_ {
// If the type is an empty tuple, return an error.
Type::Unit => self.emit_err(TypeCheckerError::lhs_must_be_identifier_or_tuple(input.span)),
// If the type is a singleton tuple, return an error.
Type::Tuple(tuple) => match tuple.length() {
0 | 1 => unreachable!("Parsing guarantees that tuple types have at least two elements."),
Expand Down Expand Up @@ -421,20 +419,12 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> {
// Set the `has_return` flag.
self.scope_state.has_return = true;

// Check that the return expression is not a nested tuple.
if let Expression::Tuple(TupleExpression { elements, .. }) = &input.expression {
for element in elements {
if matches!(element, Expression::Tuple(_)) {
self.emit_err(TypeCheckerError::nested_tuple_expression(element.span()));
}
}
}

// Set the `is_return` flag. This is necessary to allow unit expressions in the return statement.
self.scope_state.is_return = true;
// Type check the associated expression.
self.visit_expression(&input.expression, &return_type);
// Unset the `is_return` flag.
self.scope_state.is_return = false;
if let Expression::Unit(..) = input.expression {
// TODO - do will this error message be appropriate?
self.maybe_assert_type(&Type::Unit, &return_type, input.expression.span());
} else {
self.visit_expression(&input.expression, &return_type);
}
}
}
38 changes: 20 additions & 18 deletions compiler/passes/src/type_checking/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,29 +856,30 @@ impl<'a, N: Network> TypeChecker<'a, N> {
}

/// Emits an error if the type or its constituent types is not valid.
pub(crate) fn assert_type_is_valid(&mut self, type_: &Type, span: Span) -> bool {
let mut is_valid = true;
pub(crate) fn assert_type_is_valid(&mut self, type_: &Type, span: Span) {
match type_ {
// Unit types may only appear as the return type of a function.
Type::Unit => {
self.emit_err(TypeCheckerError::unit_type_only_return(span));
}
// String types are temporarily disabled.
Type::String => {
is_valid = false;
self.emit_err(TypeCheckerError::strings_are_not_supported(span));
}
// Check that the named composite type has been defined.
Type::Composite(struct_) if self.lookup_struct(struct_.program, struct_.id.name).is_none() => {
is_valid = false;
self.emit_err(TypeCheckerError::undefined_type(struct_.id.name, span));
}
// Check that the constituent types of the tuple are valid.
Type::Tuple(tuple_type) => {
for type_ in tuple_type.elements().iter() {
is_valid &= self.assert_type_is_valid(type_, span)
self.assert_type_is_valid(type_, span);
}
}
// Check that the constituent types of mapping are valid.
Type::Mapping(mapping_type) => {
is_valid &= self.assert_type_is_valid(&mapping_type.key, span);
is_valid &= self.assert_type_is_valid(&mapping_type.value, span);
self.assert_type_is_valid(&mapping_type.key, span);
self.assert_type_is_valid(&mapping_type.value, span);
}
// Check that the array element types are valid.
Type::Array(array_type) => {
Expand Down Expand Up @@ -909,11 +910,10 @@ impl<'a, N: Network> TypeChecker<'a, N> {
}
_ => {} // Do nothing.
}
is_valid &= self.assert_type_is_valid(array_type.element_type(), span)
self.assert_type_is_valid(array_type.element_type(), span);
}
_ => {} // Do nothing.
}
is_valid
}

/// Emits an error if the type is not a mapping.
Expand Down Expand Up @@ -1067,17 +1067,19 @@ impl<'a, N: Network> TypeChecker<'a, N> {
}
}
}
// Check that the type of output is defined.
if self.assert_type_is_valid(&function_output.type_, function_output.span) {
// If the function is not a transition function, then it cannot output a record.
if let Type::Composite(struct_) = function_output.type_.clone() {
if !function.variant.is_transition()
&& self.lookup_struct(struct_.program, struct_.id.name).unwrap().is_record
{
self.emit_err(TypeCheckerError::function_cannot_input_or_output_a_record(function_output.span));
}

// Check that the output type is valid.
self.assert_type_is_valid(&function_output.type_, function_output.span);

// If the function is not a transition function, then it cannot output a record.
if let Type::Composite(struct_) = function_output.type_.clone() {
if !function.variant.is_transition()
&& self.lookup_struct(struct_.program, struct_.id.name).map_or(false, |s| s.is_record)
{
self.emit_err(TypeCheckerError::function_cannot_input_or_output_a_record(function_output.span));
}
}

// Check that the type of the output is not a tuple. This is necessary to forbid nested tuples.
if matches!(&function_output.type_, Type::Tuple(_)) {
self.emit_err(TypeCheckerError::nested_tuple_type(function_output.span))
Expand Down
3 changes: 0 additions & 3 deletions compiler/passes/src/type_checking/scope_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ pub struct ScopeState {
pub(crate) variant: Option<Variant>,
/// Whether or not the function that we are currently traversing has a return statement.
pub(crate) has_return: bool,
/// Whether or not we are currently traversing a return statement.
pub(crate) is_return: bool,
/// Current program name.
pub(crate) program_name: Option<Symbol>,
/// Whether or not we are currently traversing a stub.
Expand All @@ -50,7 +48,6 @@ impl ScopeState {
function: None,
variant: None,
has_return: false,
is_return: false,
program_name: None,
is_stub: true,
futures: IndexMap::new(),
Expand Down
11 changes: 9 additions & 2 deletions errors/src/errors/type_checker/type_checker_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,7 @@ create_messages!(
help: None,
}

// TODO: Consider changing this to a warning.

// TODO This error is unused. Remove it in a future version.
@formatted
assign_unit_expression_to_variable {
args: (),
Expand Down Expand Up @@ -476,6 +475,7 @@ create_messages!(
help: None,
}

// TODO This error is unused. Remove it in a future version.
@formatted
unit_expression_only_in_return_statements {
args: (),
Expand Down Expand Up @@ -976,4 +976,11 @@ create_messages!(
),
help: Some("Valid second operands are `u8`, `u16`, or `u32`".into()),
}

@formatted
unit_type_only_return {
args: (),
msg: "The unit type () may appear only as the return type of a function.".to_string(),
help: None,
}
);
21 changes: 0 additions & 21 deletions tests/expectations/compiler/array/array_with_units_fail.out

This file was deleted.

21 changes: 3 additions & 18 deletions tests/expectations/compiler/constants/const_type_error_fail.out
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
namespace = "Compile"
expectation = "Fail"
outputs = ["""
Error [ETYC0372055]: The left-hand side of a `DefinitionStatement` can only be an identifier or tuple. Note that a tuple must contain at least two elements.
--> compiler-test:7:9
|
7 | const A: () = ();
| ^^^^^^^^^^^^^^^^
Error [ETYC0372070]: The value of a const declaration must be a literal
--> compiler-test:7:9
|
7 | const A: () = ();
| ^^^^^^^^^^^^^^^^
Error [ETYC0372056]: Unit expressions can only be used in return statements.
--> compiler-test:7:23
|
7 | const A: () = ();
| ^^
Error [ETYC0372070]: The value of a const declaration must be a literal
--> compiler-test:8:9
|
8 | const B: u8 = ((1u8,1u8),1u8);
7 | const B: u8 = ((1u8,1u8),1u8);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error [ETYC0372023]: Tuples must be explicitly typed in Leo
--> compiler-test:8:23
--> compiler-test:7:23
|
8 | const B: u8 = ((1u8,1u8),1u8);
7 | const B: u8 = ((1u8,1u8),1u8);
| ^^^^^^^^^^^^^^^
|
= The function definition must match the function return statement
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace = "Compile"
expectation = "Fail"
outputs = ["""
Error [ETYC0372055]: The left-hand side of a `DefinitionStatement` can only be an identifier or tuple. Note that a tuple must contain at least two elements.
Error [ETYC0372123]: The unit type () may appear only as the return type of a function.
--> compiler-test:11:9
|
11 | let result: () = Mapping::set(amounts, addr, amount);
Expand Down
Loading

0 comments on commit f636bed

Please sign in to comment.