mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-23 23:23:50 +03:00
enable explicit typing for futures
This commit is contained in:
parent
a2fce7a968
commit
5903635fa3
@ -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(","))
|
||||
|
@ -96,11 +96,13 @@ 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
|
||||
.inputs()
|
||||
.iter()
|
||||
.zip_eq(right.inputs().iter())
|
||||
.all(|(left_type, right_type)| left_type.eq_flat(right_type)),
|
||||
(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())
|
||||
.all(|(left_type, right_type)| left_type.eq_flat(right_type)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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_,
|
||||
},
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user