enable explicit typing for futures

This commit is contained in:
evan-schott 2024-04-09 11:36:55 -07:00
parent a2fce7a968
commit 5903635fa3
6 changed files with 79 additions and 74 deletions

View File

@ -18,20 +18,23 @@ use crate::{Location, Type};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fmt::Display;
/// A future type consisting of the type of the inputs.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FutureType {
// Optional type specification of inputs.
pub inputs: Vec<Type>,
// The location of the function that produced the future.
pub location: Option<Location>
pub location: Option<Location>,
// Whether or not the type has been explicitly specified.
pub is_explicit: bool,
}
impl FutureType {
/// Initialize a new future type.
pub fn new(inputs: Vec<Type>, location: Option<Location>) -> Self {
Self { inputs, location }
pub fn new(inputs: Vec<Type>, location: Option<Location>, is_explicit: bool) -> Self {
Self { inputs, location, is_explicit }
}
/// Returns the inputs of the future type.
@ -45,6 +48,12 @@ impl FutureType {
}
}
impl Default for crate::FutureType {
fn default() -> Self {
Self::new(vec![], None, false)
}
}
impl fmt::Display for crate::FutureType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Future<{}>", self.inputs.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","))

View File

@ -96,7 +96,9 @@ impl Type {
(Type::Composite(left), Type::Composite(right)) => {
left.id.name == right.id.name && left.program == right.program
}
(Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() => left
(Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() =>
left.location == right.location &&
left
.inputs()
.iter()
.zip_eq(right.inputs().iter())

View File

@ -16,7 +16,8 @@
use crate::TypeChecker;
use leo_ast::*;
use leo_ast::{*,
Variant::{AsyncFunction, AsyncTransition}};
use leo_errors::{emitter::Handler, TypeCheckerError};
use leo_span::{sym, Span, Symbol};
@ -27,7 +28,6 @@ use leo_ast::{
};
use snarkvm::console::network::{MainnetV0, Network};
use std::str::FromStr;
use leo_ast::Variant::{Transition, Function, AsyncFunction, AsyncTransition};
fn return_incorrect_type(t1: Option<Type>, t2: Option<Type>, expected: &Option<Type>) -> Option<Type> {
match (t1, t2) {
@ -169,6 +169,8 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
Future(_) => {
// Get the fully inferred type.
if let Some(Type::Future(inferred_f)) = self.type_table.get(&access.tuple.id()) {
dbg!(inferred_f.clone());
dbg!(access.clone());
// Make sure in range.
if access.index.value() >= inferred_f.inputs().len() {
self.emit_err(TypeCheckerError::invalid_future_access(
@ -628,12 +630,38 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
}
// Async functions return a single future.
let mut ret = if func.variant == AsyncFunction {
// Type check after know the input types.
if let Some(Type::Future(_)) = expected {
Type::Future(FutureType::new(Vec::new(), Some(Location::new(input.program, ident.name))))
Type::Future(FutureType::new(Vec::new(), Some(Location::new(input.program, ident.name)), false))
} else {
self.emit_err(TypeCheckerError::return_type_of_finalize_function_is_future(input.span));
Type::Unit
}
} else if func.variant == AsyncTransition {
// Fully infer future type.
let future_type = Type::Future(FutureType::new(
// Assumes that external function stubs have been processed.
self.finalize_input_types
.get(&Location::new(
input.program,
Symbol::intern(&format!("finalize/{}", ident.name)),
))
.unwrap()
.clone(),
Some(Location::new(input.program, ident.name)),
true
));
let fully_inferred_type = match func.output_type {
Tuple(tup) => Tuple(TupleType::new(
tup.elements()
.iter()
.map(|t| if matches!(t, Future(_)) { future_type.clone() } else { t.clone() })
.collect::<Vec<Type>>(),
)),
Future(_) => future_type,
_ => panic!("Invalid output type for async transition."),
};
self.assert_and_return_type(fully_inferred_type, expected, input.span())
} else {
self.assert_and_return_type(func.output_type, expected, input.span())
};
@ -718,35 +746,10 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
input.span,
));
}
// Fully infer future type.
let future_type = Type::Future(FutureType::new(
// Assumes that external function stubs have been processed.
self.finalize_input_types
.get(&Location::new(
input.program,
Symbol::intern(&format!("finalize/{}", ident.name)),
))
.unwrap()
.clone(),
Some(Location::new(input.program, ident.name))
));
ret = match ret.clone() {
Tuple(tup) => Tuple(TupleType::new(
tup.elements()
.iter()
.map(|t| if matches!(t, Future(_)) { future_type.clone() } else { t.clone() })
.collect::<Vec<Type>>(),
)),
Future(_) => future_type,
_ => {
self.emit_err(TypeCheckerError::async_transition_invalid_output(input.span));
ret
}
};
} else if func.variant.is_function() {
// Can only call an async function once in a transition function body.
if self.scope_state.has_called_finalize {
self.emit_err(TypeCheckerError::must_call_finalize_once(input.span));
self.emit_err(TypeCheckerError::must_call_async_function_once(input.span));
}
// Check that all futures consumed.
if !self.scope_state.futures.is_empty() {
@ -775,7 +778,10 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
self.scope_state.has_called_finalize = true;
// Update ret to reflect fully inferred future type.
ret = Type::Future(FutureType::new(inferred_finalize_inputs, Some(Location::new(input.program, ident.name))));
ret = Type::Future(FutureType::new(inferred_finalize_inputs, Some(Location::new(input.program, ident.name)), true));
// Type check in case the expected type is known.
self.assert_and_return_type(ret.clone(), expected, input.span());
}
// Set call location so that definition statement knows where future comes from.
self.scope_state.call_location = Some(Location::new(input.program, ident.name));

View File

@ -16,16 +16,14 @@
use crate::{DiGraphError, TypeChecker};
use leo_ast::*;
use leo_ast::{*,
Input::{External, Internal},
Type::Future,
};
use leo_errors::{TypeCheckerError, TypeCheckerWarning};
use leo_span::sym;
use snarkvm::console::network::{MainnetV0, Network};
use leo_ast::{
Input::{External, Internal},
Type::Future,
};
use std::collections::HashSet;
use leo_ast::Variant::{AsyncFunction, AsyncTransition};
@ -100,7 +98,7 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
Future(f) => {
// Since we traverse stubs in post-order, we can assume that the corresponding finalize stub has already been traversed.
Future(FutureType::new(
finalize_input_map.get(&f.location.clone().unwrap()).unwrap().clone(), f.location.clone()))
finalize_input_map.get(&f.location.clone().unwrap()).unwrap().clone(), f.location.clone(), true))
}
_ => function_input.clone().type_,
},

View File

@ -122,7 +122,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
let current_bst_nodes: Vec<ConditionalTreeNode> =
match self.await_checker.create_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), input.span) {
Ok(nodes) => nodes,
Err(err) => return self.emit_err(err),
Err(warn) => return self.emit_warning(warn),
};
// Visit block.
@ -386,7 +386,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
fn visit_return(&mut self, input: &'a ReturnStatement) {
// Cannot return anything from finalize.
if self.scope_state.variant == Some(Variant::AsyncFunction) {
self.emit_err(TypeCheckerError::return_in_finalize(input.span()));
self.emit_err(TypeCheckerError::finalize_function_cannot_return_value(input.span()));
}
// We can safely unwrap all self.parent instances because
// statements should always have some parent block
@ -398,13 +398,13 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
// Fully type the expected return value.
if self.scope_state.variant == Some(Variant::AsyncTransition) && self.scope_state.has_called_finalize {
let inferred_future_type = match self.finalize_input_types.get(&func.unwrap().finalize.clone().unwrap()) {
Some(types) => Future(FutureType::new(types.clone(), Some(Location::new(self.scope_state.program_name, parent)))),
Some(types) => Future(FutureType::new(types.clone(), Some(Location::new(self.scope_state.program_name, parent)), true)),
None => {
return self.emit_err(TypeCheckerError::async_transition_missing_future_to_return(input.span()));
}
};
// Need to modify return type since the function signature is just default future, but the actual return type is the fully inferred future of the finalize input type.
return_type = match return_type {
let inferred = match return_type.clone() {
Some(Future(_)) => Some(inferred_future_type),
Some(Tuple(tuple)) => Some(Tuple(TupleType::new(
tuple
@ -416,6 +416,11 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
_ => {
return self.emit_err(TypeCheckerError::async_transition_missing_future_to_return(input.span()));
}
};
// Check that the explicit type declared in the function output signature matches the inferred type.
if let Some(ty) = inferred {
return_type = Some(self.assert_and_return_type(ty, &return_type, input.span()));
}
}

View File

@ -16,23 +16,7 @@
use crate::{CallGraph, StructGraph, SymbolTable, TypeTable, VariableSymbol, VariableType};
use leo_ast::{
Composite,
CompositeType,
CoreConstant,
CoreFunction,
Expression,
Function,
Identifier,
IntegerType,
Location,
MappingType,
Mode,
Node,
Output,
Type,
Variant,
};
use leo_ast::{Composite, CompositeType, CoreConstant, CoreFunction, Expression, Function, FutureType, Identifier, IntegerType, Location, MappingType, Mode, Node, Output, Type, Variant};
use leo_errors::{emitter::Handler, TypeCheckerError, TypeCheckerWarning};
use leo_span::{Span, Symbol};
@ -167,7 +151,7 @@ impl<'a> TypeChecker<'a> {
}
/// Emits a type checker warning
pub(crate) fn emit_warning(&self, warning: TypeCheckerWarning) {
pub fn emit_warning(&self, warning: TypeCheckerWarning) {
self.handler.emit_warning(warning.into());
}
@ -182,12 +166,12 @@ impl<'a> TypeChecker<'a> {
/// Determines if the two types have the same structure.
/// Needs access to the symbol table in order to compare nested future and struct types.
pub(crate) fn check_eq_type_structure(&self, t1: &Type, t2: &Type, span: Span) -> bool {
if t1.eq_flat(t2) {
pub(crate) fn check_eq_type_structure(&self, actual: &Type, expected: &Type, span: Span) -> bool {
if actual.eq_flat(expected) {
return true;
}
// All of these types could return false for `eq_flat` if they have an external struct.
match (t1, t2) {
match (actual, expected) {
(Type::Array(left), Type::Array(right)) => {
self.check_eq_type_structure(left.element_type(), right.element_type(), span)
&& left.length() == right.length()
@ -219,6 +203,8 @@ impl<'a> TypeChecker<'a> {
true
}
}
// Don't type check when type hasn't been explicitly defined.
(Type::Future(left), Type::Future(right)) if !left.is_explicit || !right.is_explicit => true,
(Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() => left
.inputs()
.iter()
@ -1287,9 +1273,7 @@ impl<'a> TypeChecker<'a> {
function.input.iter().zip_eq(inferred_input_types.iter()).for_each(|(t1, t2)| {
if let Internal(fn_input) = t1 {
// Allow partial type matching of futures since inferred are fully typed, whereas AST has default futures.
if !(matches!(t2, Type::Future(_)) && matches!(fn_input.type_, Type::Future(_))) {
self.check_eq_types(&Some(t1.type_()), &Some(t2.clone()), t1.span())
} else {
if matches!(t2, Type::Future(_)) && matches!(fn_input.type_, Type::Future(_)) {
// Insert to symbol table
if let Err(err) = self.symbol_table.borrow_mut().insert_variable(
Location::new(None, fn_input.identifier.name),
@ -1302,6 +1286,7 @@ impl<'a> TypeChecker<'a> {
self.handler.emit_err(err);
}
}
self.check_eq_types(&Some(t1.type_()), &Some(t2.clone()), t1.span())
}
});
} else {