Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions compiler/ast/src/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,81 @@ impl Default for Expression {
}
}

#[allow(clippy::type_complexity)]
pub fn zero_value_expression(
ty: &Type,
span: Span,
node_builder: &NodeBuilder,
struct_lookup: &dyn Fn(&[Symbol]) -> Vec<(Symbol, Type)>,
) -> Option<Expression> {
let id = node_builder.next_id();

match ty {
// Numeric types
Type::Integer(IntegerType::I8) => Some(Literal::integer(IntegerType::I8, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::I16) => Some(Literal::integer(IntegerType::I16, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::I32) => Some(Literal::integer(IntegerType::I32, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::I64) => Some(Literal::integer(IntegerType::I64, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::I128) => Some(Literal::integer(IntegerType::I128, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::U8) => Some(Literal::integer(IntegerType::U8, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::U16) => Some(Literal::integer(IntegerType::U16, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::U32) => Some(Literal::integer(IntegerType::U32, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::U64) => Some(Literal::integer(IntegerType::U64, "0".to_string(), span, id).into()),
Type::Integer(IntegerType::U128) => Some(Literal::integer(IntegerType::U128, "0".to_string(), span, id).into()),

// Boolean
Type::Boolean => Some(Literal::boolean(false, span, id).into()),

// Field, Group, Scalar
Type::Field => Some(Literal::field("0".to_string(), span, id).into()),
Type::Group => Some(Literal::group("0".to_string(), span, id).into()),
Type::Scalar => Some(Literal::scalar("0".to_string(), span, id).into()),

// Structs (composite types)
Type::Composite(composite_type) => {
let path = &composite_type.path;
let members = struct_lookup(&path.absolute_path());

let struct_members = members
.into_iter()
.map(|(symbol, member_type)| {
let member_id = node_builder.next_id();
let zero_expr = zero_value_expression(&member_type, span, node_builder, struct_lookup)?;

Some(StructVariableInitializer {
span,
id: member_id,
identifier: crate::Identifier::new(symbol, node_builder.next_id()),
expression: Some(zero_expr),
})
})
.collect::<Option<Vec<_>>>()?;

Some(Expression::Struct(StructExpression {
span,
id,
path: path.clone(),
const_arguments: composite_type.const_arguments.clone(),
members: struct_members,
}))
}

// Arrays
Type::Array(array_type) => {
let element_ty = &array_type.element_type;

let element_expr = zero_value_expression(element_ty, span, node_builder, struct_lookup)?;

Some(Expression::Repeat(
RepeatExpression { span, id, expr: element_expr, count: *array_type.length.clone() }.into(),
))
}

// Other types are not expected or supported just yet
_ => None,
}
}

impl Node for Expression {
fn span(&self) -> Span {
use Expression::*;
Expand Down
34 changes: 30 additions & 4 deletions compiler/ast/src/interpreter_value/core_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::{
CoreFunction,
Expression,
Type,
halt2,
interpreter_value::{ExpectTc, Value},
tc_fail2,
};
Expand Down Expand Up @@ -300,12 +301,22 @@ pub fn evaluate_core_function(
helper.mapping_get(program, name, &key).is_some().into()
}
CoreFunction::OptionalUnwrap => {
// TODO
return Ok(None);
let (is_some, val) = unpack_option_struct(helper.pop_value().expect_tc(span)?.contents, span)?;

if is_some {
Value { id: None, contents: ValueVariants::Svm(val.into()) }
} else {
halt2!(span, "called unwrap on a none value")
}
}
CoreFunction::OptionalUnwrapOr => {
// TODO
return Ok(None);
let (is_some, val) = unpack_option_struct(helper.pop_value().expect_tc(span)?.contents, span)?;

let ValueVariants::Svm(SvmValue::Plaintext(or)) = helper.pop_value().expect_tc(span)?.contents else {
tc_fail2!()
};

Value { id: None, contents: ValueVariants::Svm(if is_some { val.into() } else { or.into() }) }
}
CoreFunction::VectorPush
| CoreFunction::VectorLen
Expand Down Expand Up @@ -333,3 +344,18 @@ pub fn evaluate_core_function(

Ok(Some(value))
}

fn unpack_option_struct(v: ValueVariants, span: Span) -> Result<(bool, SvmPlaintext)> {
let ValueVariants::Svm(SvmValue::Plaintext(Plaintext::Struct(members, _))) = v else { tc_fail2!() };

let get = |name: &str| {
let id = Symbol::intern(name).to_string().parse::<SvmIdentifier>().expect("type checker failure");
members.get(&id).expect_tc(span)
};

let SvmPlaintext::Literal(SvmLiteral::Boolean(is_some), _) = get("is_some")? else { tc_fail2!() };

let val = get("val")?.clone();

Ok((**is_some, val))
}
26 changes: 22 additions & 4 deletions compiler/ast/src/interpreter_value/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ pub struct StructContents {
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct Value {
pub id: Option<Location>,
pub(crate) contents: ValueVariants,
pub contents: ValueVariants,
}

#[derive(Clone, Default, Debug, Eq, PartialEq)]
// SnarkVM's Value is large, but that's okay.
#[allow(clippy::large_enum_variant)]
pub(crate) enum ValueVariants {
pub enum ValueVariants {
#[default]
Unit,
Svm(SvmValue),
Expand All @@ -93,7 +93,7 @@ pub(crate) enum ValueVariants {
String(String),
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AsyncExecution {
AsyncFunctionCall {
function: Location,
Expand All @@ -102,10 +102,28 @@ pub enum AsyncExecution {
AsyncBlock {
containing_function: Location, // The function that contains the async block.
block: crate::NodeID,
names: BTreeMap<Vec<Symbol>, Value>, // Use a `BTreeMap` here because `HashMap` does not implement `Hash`.
names: BTreeMap<Vec<Symbol>, (Value, Option<Type>)>, // Use a `BTreeMap` here because `HashMap` does not implement `Hash`.
},
}

impl Hash for AsyncExecution {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::AsyncBlock { containing_function, block, names } => {
0u8.hash(state);
containing_function.hash(state);
block.hash(state);
names.iter().map(|(k, v)| (k.clone(), v.0.clone())).collect::<BTreeMap<_, _>>().hash(state);
}
Self::AsyncFunctionCall { function, arguments } => {
1u8.hash(state);
function.hash(state);
arguments.hash(state);
}
}
}
}

impl fmt::Display for AsyncExecution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, " async call to ")?;
Expand Down
2 changes: 1 addition & 1 deletion compiler/ast/src/statement/definition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use std::fmt;

/// A `let` or `const` declaration statement.
/// A `let` declaration statement.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct DefinitionStatement {
/// The bindings / variable names to declare.
Expand Down
41 changes: 40 additions & 1 deletion compiler/ast/src/types/optional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use crate::Type;
use crate::{ArrayType, CompositeType, Type};

use itertools::Itertools;
use leo_span::Symbol;
use serde::{Deserialize, Serialize};
use std::fmt;

Expand All @@ -25,6 +27,43 @@ pub struct OptionalType {
pub inner: Box<Type>,
}

pub fn make_optional_struct_symbol(ty: &Type) -> Symbol {
// Step 1: Extract a usable type name
fn display_type(ty: &Type) -> String {
match ty {
Type::Address
| Type::Field
| Type::Group
| Type::Scalar
| Type::Signature
| Type::Boolean
| Type::Integer(..) => format!("{ty}"),
Type::Array(ArrayType { element_type, length }) => {
format!("[{}; {length}]", display_type(element_type))
}
Type::Composite(CompositeType { path, .. }) => {
format!("::{}", path.absolute_path().iter().format("::"))
}

Type::Tuple(_)
| Type::Optional(_)
| Type::Mapping(_)
| Type::Numeric
| Type::Identifier(_)
| Type::Future(_)
| Type::Vector(_)
| Type::String
| Type::Err
| Type::Unit => {
panic!("unexpected inner type in optional struct name")
}
}
}

// Step 3: Build symbol that ends with `?`.
Symbol::intern(&format!("\"{}?\"", display_type(ty)))
}

impl fmt::Display for OptionalType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}?", self.inner)
Expand Down
29 changes: 23 additions & 6 deletions compiler/passes/src/const_propagation/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,9 @@ impl AstReconstructor for ConstPropagationVisitor<'_> {
) -> (Expression, Self::AdditionalOutput) {
let type_info = self.state.type_table.get(&input.id());

// If this is an optional, then unwrap it first.
let type_info = type_info.as_ref().map(|ty| match ty {
Type::Optional(opt) => *opt.inner.clone(),
_ => ty.clone(),
});
if let Some(Type::Optional(_)) = type_info {
return (input.into(), None);
}

if let Ok(value) = interpreter_value::literal_to_value(&input, &type_info) {
// If we know the type of an unsuffixed literal, might as well change it to a suffixed literal. This way, we
Expand Down Expand Up @@ -536,7 +534,26 @@ impl AstReconstructor for ConstPropagationVisitor<'_> {
}

fn reconstruct_const(&mut self, mut input: ConstDeclaration) -> (Statement, Self::AdditionalOutput) {
if matches!(input.type_, Type::Optional(_)) {
// If there is any optional in the type definition, leave it for now.
fn recursive_optional(type_: &Type, slf: &ConstPropagationVisitor) -> bool {
match type_ {
Type::Array(array_type) => recursive_optional(array_type.element_type(), slf),
Type::Composite(composite_type) => {
if let Some(cmp) =
slf.state.symbol_table.lookup_struct(composite_type.path.absolute_path().as_ref())
{
cmp.members.iter().any(|mbr| recursive_optional(&mbr.type_, slf))
} else {
false
}
}
Type::Optional(_) => true,
Type::Tuple(tuple_type) => tuple_type.elements.iter().any(|element| recursive_optional(element, slf)),
_ => false,
}
}

if recursive_optional(&input.type_, self) {
return (input.into(), None);
}

Expand Down
54 changes: 13 additions & 41 deletions compiler/passes/src/option_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,21 @@
//! After this pass, no `T?` types remain in the program: all optional values are represented explicitly
//! as structs with `is_some` and `val` fields.

use crate::{Pass, PathResolution, SymbolTable, SymbolTableCreation, TypeChecking, TypeCheckingInput};

use leo_ast::{ArrayType, CompositeType, ProgramReconstructor as _, Type};
use crate::{
ConstPropagation,
Pass,
PathResolution,
SymbolTable,
SymbolTableCreation,
TypeChecking,
TypeCheckingInput,
};

use leo_ast::ProgramReconstructor as _;
use leo_errors::Result;
use leo_span::Symbol;

use indexmap::IndexMap;
use itertools::Itertools;

mod ast;

Expand Down Expand Up @@ -101,44 +108,9 @@ impl Pass for OptionLowering {
PathResolution::do_pass((), state)?;
SymbolTableCreation::do_pass((), state)?;
TypeChecking::do_pass(input.clone(), state)?;
// Now there are no more optionals, we can now evaluate the core unwrap functions of const optionals in the interpreter.
ConstPropagation::do_pass((), state)?;

Ok(())
}
}

pub fn make_optional_struct_symbol(ty: &Type) -> Symbol {
// Step 1: Extract a usable type name
fn display_type(ty: &Type) -> String {
match ty {
Type::Address
| Type::Field
| Type::Group
| Type::Scalar
| Type::Signature
| Type::Boolean
| Type::Integer(..) => format!("{ty}"),
Type::Array(ArrayType { element_type, length }) => {
format!("[{}; {length}]", display_type(element_type))
}
Type::Composite(CompositeType { path, .. }) => {
format!("::{}", path.absolute_path().iter().format("::"))
}

Type::Tuple(_)
| Type::Optional(_)
| Type::Mapping(_)
| Type::Numeric
| Type::Identifier(_)
| Type::Future(_)
| Type::Vector(_)
| Type::String
| Type::Err
| Type::Unit => {
panic!("unexpected inner type in optional struct name")
}
}
}

// Step 3: Build symbol that ends with `?`.
Symbol::intern(&format!("\"{}?\"", display_type(ty)))
}
2 changes: 1 addition & 1 deletion compiler/passes/src/option_lowering/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl OptionLoweringVisitor<'_> {
/// If the struct for this type already exists, it’s reused; otherwise, a new one is created.
/// Returns the `Symbol` for the struct name.
pub fn insert_optional_wrapper_struct(&mut self, ty: &Type) -> Symbol {
let struct_name = crate::make_optional_struct_symbol(ty);
let struct_name = make_optional_struct_symbol(ty);

self.new_structs.entry(struct_name).or_insert_with(|| Composite {
identifier: Identifier::new(struct_name, self.state.node_builder.next_id()),
Expand Down
Loading