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 serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// A future type consisting of the type of the inputs.
|
/// 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 {
|
pub struct FutureType {
|
||||||
// Optional type specification of inputs.
|
// Optional type specification of inputs.
|
||||||
pub inputs: Vec<Type>,
|
pub inputs: Vec<Type>,
|
||||||
// The location of the function that produced the future.
|
// 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 {
|
impl FutureType {
|
||||||
/// Initialize a new future type.
|
/// Initialize a new future type.
|
||||||
pub fn new(inputs: Vec<Type>, location: Option<Location>) -> Self {
|
pub fn new(inputs: Vec<Type>, location: Option<Location>, is_explicit: bool) -> Self {
|
||||||
Self { inputs, location }
|
Self { inputs, location, is_explicit }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inputs of the future type.
|
/// 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 {
|
impl fmt::Display for crate::FutureType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Future<{}>", self.inputs.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","))
|
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)) => {
|
(Type::Composite(left), Type::Composite(right)) => {
|
||||||
left.id.name == right.id.name && left.program == right.program
|
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() =>
|
||||||
.inputs()
|
left.location == right.location &&
|
||||||
.iter()
|
left
|
||||||
.zip_eq(right.inputs().iter())
|
.inputs()
|
||||||
.all(|(left_type, right_type)| left_type.eq_flat(right_type)),
|
.iter()
|
||||||
|
.zip_eq(right.inputs().iter())
|
||||||
|
.all(|(left_type, right_type)| left_type.eq_flat(right_type)),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
use crate::TypeChecker;
|
use crate::TypeChecker;
|
||||||
|
|
||||||
use leo_ast::*;
|
use leo_ast::{*,
|
||||||
|
Variant::{AsyncFunction, AsyncTransition}};
|
||||||
use leo_errors::{emitter::Handler, TypeCheckerError};
|
use leo_errors::{emitter::Handler, TypeCheckerError};
|
||||||
use leo_span::{sym, Span, Symbol};
|
use leo_span::{sym, Span, Symbol};
|
||||||
|
|
||||||
@ -27,7 +28,6 @@ use leo_ast::{
|
|||||||
};
|
};
|
||||||
use snarkvm::console::network::{MainnetV0, Network};
|
use snarkvm::console::network::{MainnetV0, Network};
|
||||||
use std::str::FromStr;
|
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> {
|
fn return_incorrect_type(t1: Option<Type>, t2: Option<Type>, expected: &Option<Type>) -> Option<Type> {
|
||||||
match (t1, t2) {
|
match (t1, t2) {
|
||||||
@ -169,6 +169,8 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
|
|||||||
Future(_) => {
|
Future(_) => {
|
||||||
// Get the fully inferred type.
|
// Get the fully inferred type.
|
||||||
if let Some(Type::Future(inferred_f)) = self.type_table.get(&access.tuple.id()) {
|
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.
|
// Make sure in range.
|
||||||
if access.index.value() >= inferred_f.inputs().len() {
|
if access.index.value() >= inferred_f.inputs().len() {
|
||||||
self.emit_err(TypeCheckerError::invalid_future_access(
|
self.emit_err(TypeCheckerError::invalid_future_access(
|
||||||
@ -628,12 +630,38 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
|
|||||||
}
|
}
|
||||||
// Async functions return a single future.
|
// Async functions return a single future.
|
||||||
let mut ret = if func.variant == AsyncFunction {
|
let mut ret = if func.variant == AsyncFunction {
|
||||||
|
// Type check after know the input types.
|
||||||
if let Some(Type::Future(_)) = expected {
|
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 {
|
} else {
|
||||||
self.emit_err(TypeCheckerError::return_type_of_finalize_function_is_future(input.span));
|
self.emit_err(TypeCheckerError::return_type_of_finalize_function_is_future(input.span));
|
||||||
Type::Unit
|
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 {
|
} else {
|
||||||
self.assert_and_return_type(func.output_type, expected, input.span())
|
self.assert_and_return_type(func.output_type, expected, input.span())
|
||||||
};
|
};
|
||||||
@ -718,35 +746,10 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
|
|||||||
input.span,
|
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() {
|
} else if func.variant.is_function() {
|
||||||
// Can only call an async function once in a transition function body.
|
// Can only call an async function once in a transition function body.
|
||||||
if self.scope_state.has_called_finalize {
|
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.
|
// Check that all futures consumed.
|
||||||
if !self.scope_state.futures.is_empty() {
|
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;
|
self.scope_state.has_called_finalize = true;
|
||||||
|
|
||||||
// Update ret to reflect fully inferred future type.
|
// 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.
|
// Set call location so that definition statement knows where future comes from.
|
||||||
self.scope_state.call_location = Some(Location::new(input.program, ident.name));
|
self.scope_state.call_location = Some(Location::new(input.program, ident.name));
|
||||||
|
@ -16,16 +16,14 @@
|
|||||||
|
|
||||||
use crate::{DiGraphError, TypeChecker};
|
use crate::{DiGraphError, TypeChecker};
|
||||||
|
|
||||||
use leo_ast::*;
|
use leo_ast::{*,
|
||||||
|
Input::{External, Internal},
|
||||||
|
Type::Future,
|
||||||
|
};
|
||||||
use leo_errors::{TypeCheckerError, TypeCheckerWarning};
|
use leo_errors::{TypeCheckerError, TypeCheckerWarning};
|
||||||
use leo_span::sym;
|
use leo_span::sym;
|
||||||
|
|
||||||
use snarkvm::console::network::{MainnetV0, Network};
|
use snarkvm::console::network::{MainnetV0, Network};
|
||||||
|
|
||||||
use leo_ast::{
|
|
||||||
Input::{External, Internal},
|
|
||||||
Type::Future,
|
|
||||||
};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use leo_ast::Variant::{AsyncFunction, AsyncTransition};
|
use leo_ast::Variant::{AsyncFunction, AsyncTransition};
|
||||||
|
|
||||||
@ -100,7 +98,7 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
|
|||||||
Future(f) => {
|
Future(f) => {
|
||||||
// Since we traverse stubs in post-order, we can assume that the corresponding finalize stub has already been traversed.
|
// Since we traverse stubs in post-order, we can assume that the corresponding finalize stub has already been traversed.
|
||||||
Future(FutureType::new(
|
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_,
|
_ => function_input.clone().type_,
|
||||||
},
|
},
|
||||||
|
@ -122,7 +122,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
let current_bst_nodes: Vec<ConditionalTreeNode> =
|
let current_bst_nodes: Vec<ConditionalTreeNode> =
|
||||||
match self.await_checker.create_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), input.span) {
|
match self.await_checker.create_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), input.span) {
|
||||||
Ok(nodes) => nodes,
|
Ok(nodes) => nodes,
|
||||||
Err(err) => return self.emit_err(err),
|
Err(warn) => return self.emit_warning(warn),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Visit block.
|
// Visit block.
|
||||||
@ -386,7 +386,7 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
fn visit_return(&mut self, input: &'a ReturnStatement) {
|
fn visit_return(&mut self, input: &'a ReturnStatement) {
|
||||||
// Cannot return anything from finalize.
|
// Cannot return anything from finalize.
|
||||||
if self.scope_state.variant == Some(Variant::AsyncFunction) {
|
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
|
// We can safely unwrap all self.parent instances because
|
||||||
// statements should always have some parent block
|
// statements should always have some parent block
|
||||||
@ -398,13 +398,13 @@ impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
|
|||||||
// Fully type the expected return value.
|
// Fully type the expected return value.
|
||||||
if self.scope_state.variant == Some(Variant::AsyncTransition) && self.scope_state.has_called_finalize {
|
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()) {
|
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 => {
|
None => {
|
||||||
return self.emit_err(TypeCheckerError::async_transition_missing_future_to_return(input.span()));
|
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.
|
// 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(Future(_)) => Some(inferred_future_type),
|
||||||
Some(Tuple(tuple)) => Some(Tuple(TupleType::new(
|
Some(Tuple(tuple)) => Some(Tuple(TupleType::new(
|
||||||
tuple
|
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()));
|
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 crate::{CallGraph, StructGraph, SymbolTable, TypeTable, VariableSymbol, VariableType};
|
||||||
|
|
||||||
use leo_ast::{
|
use leo_ast::{Composite, CompositeType, CoreConstant, CoreFunction, Expression, Function, FutureType, Identifier, IntegerType, Location, MappingType, Mode, Node, Output, Type, Variant};
|
||||||
Composite,
|
|
||||||
CompositeType,
|
|
||||||
CoreConstant,
|
|
||||||
CoreFunction,
|
|
||||||
Expression,
|
|
||||||
Function,
|
|
||||||
Identifier,
|
|
||||||
IntegerType,
|
|
||||||
Location,
|
|
||||||
MappingType,
|
|
||||||
Mode,
|
|
||||||
Node,
|
|
||||||
Output,
|
|
||||||
Type,
|
|
||||||
Variant,
|
|
||||||
};
|
|
||||||
use leo_errors::{emitter::Handler, TypeCheckerError, TypeCheckerWarning};
|
use leo_errors::{emitter::Handler, TypeCheckerError, TypeCheckerWarning};
|
||||||
use leo_span::{Span, Symbol};
|
use leo_span::{Span, Symbol};
|
||||||
|
|
||||||
@ -167,7 +151,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emits a type checker warning
|
/// 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());
|
self.handler.emit_warning(warning.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,12 +166,12 @@ impl<'a> TypeChecker<'a> {
|
|||||||
|
|
||||||
/// Determines if the two types have the same structure.
|
/// Determines if the two types have the same structure.
|
||||||
/// Needs access to the symbol table in order to compare nested future and struct types.
|
/// 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 {
|
pub(crate) fn check_eq_type_structure(&self, actual: &Type, expected: &Type, span: Span) -> bool {
|
||||||
if t1.eq_flat(t2) {
|
if actual.eq_flat(expected) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// All of these types could return false for `eq_flat` if they have an external struct.
|
// 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)) => {
|
(Type::Array(left), Type::Array(right)) => {
|
||||||
self.check_eq_type_structure(left.element_type(), right.element_type(), span)
|
self.check_eq_type_structure(left.element_type(), right.element_type(), span)
|
||||||
&& left.length() == right.length()
|
&& left.length() == right.length()
|
||||||
@ -219,6 +203,8 @@ impl<'a> TypeChecker<'a> {
|
|||||||
true
|
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
|
(Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() => left
|
||||||
.inputs()
|
.inputs()
|
||||||
.iter()
|
.iter()
|
||||||
@ -1287,9 +1273,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
function.input.iter().zip_eq(inferred_input_types.iter()).for_each(|(t1, t2)| {
|
function.input.iter().zip_eq(inferred_input_types.iter()).for_each(|(t1, t2)| {
|
||||||
if let Internal(fn_input) = t1 {
|
if let Internal(fn_input) = t1 {
|
||||||
// Allow partial type matching of futures since inferred are fully typed, whereas AST has default futures.
|
// 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(_))) {
|
if matches!(t2, Type::Future(_)) && matches!(fn_input.type_, Type::Future(_)) {
|
||||||
self.check_eq_types(&Some(t1.type_()), &Some(t2.clone()), t1.span())
|
|
||||||
} else {
|
|
||||||
// Insert to symbol table
|
// Insert to symbol table
|
||||||
if let Err(err) = self.symbol_table.borrow_mut().insert_variable(
|
if let Err(err) = self.symbol_table.borrow_mut().insert_variable(
|
||||||
Location::new(None, fn_input.identifier.name),
|
Location::new(None, fn_input.identifier.name),
|
||||||
@ -1302,6 +1286,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
self.handler.emit_err(err);
|
self.handler.emit_err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.check_eq_types(&Some(t1.type_()), &Some(t2.clone()), t1.span())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user