Merge pull request #1901 from AleoHQ/record-type

Implement record type
This commit is contained in:
Collin Chin 2022-07-02 17:15:16 -07:00 committed by GitHub
commit 0d0a8a5364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 476 additions and 148 deletions

View File

@ -1,29 +0,0 @@
{
"**/*.rs": [
"// Copyright (C) 2019-2022 Aleo Systems Inc.",
"// This file is part of the Leo library.",
"",
"// The Leo library is free software: you can redistribute it and/or modify",
"// it under the terms of the GNU General Public License as published by",
"// the Free Software Foundation, either version 3 of the License, or",
"// (at your option) any later version.",
"",
"// The Leo library is distributed in the hope that it will be useful,",
"// but WITHOUT ANY WARRANTY; without even the implied warranty of",
"// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
"// GNU General Public License for more details.",
"",
"// 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/>.",
""
],
"ignore": [
".cargo",
".github",
".resources",
".travis",
"examples",
"target/"
]
}

View File

@ -12,4 +12,4 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

View File

@ -57,8 +57,9 @@ fn check_file_licenses<P: AsRef<Path>>(path: P) {
.read_to_end(&mut contents) .read_to_end(&mut contents)
.unwrap(); .unwrap();
assert!( assert_eq!(
contents == EXPECTED_LICENSE_TEXT, contents,
EXPECTED_LICENSE_TEXT,
"The license in \"{}\" is either missing or it doesn't match the expected string!", "The license in \"{}\" is either missing or it doesn't match the expected string!",
entry.path().display() entry.path().display()
); );
@ -72,5 +73,8 @@ fn check_file_licenses<P: AsRef<Path>>(path: P) {
// The build script; it currently only checks the licenses. // The build script; it currently only checks the licenses.
fn main() { fn main() {
// Check licenses in the current folder. // Check licenses in the current folder.
check_file_licenses("."); if !cfg!(target_os = "windows") {
// disable license check for windows for now.
check_file_licenses(".");
}
} }

View File

@ -32,6 +32,9 @@ pub struct Circuit {
pub identifier: Identifier, pub identifier: Identifier,
/// The fields, constant variables, and functions of this structure. /// The fields, constant variables, and functions of this structure.
pub members: Vec<CircuitMember>, pub members: Vec<CircuitMember>,
/// Was this a `record Foo { ... }`?
/// If so, it wasn't a circuit.
pub is_record: bool,
/// The entire span of the circuit definition. /// The entire span of the circuit definition.
pub span: Span, pub span: Span,
} }
@ -49,9 +52,18 @@ impl Circuit {
pub fn name(&self) -> Symbol { pub fn name(&self) -> Symbol {
self.identifier.name self.identifier.name
} }
}
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result { impl fmt::Debug for Circuit {
writeln!(f, "circuit {} {{ ", self.identifier)?; fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Display>::fmt(self, f)
}
}
impl fmt::Display for Circuit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(if self.is_record { "record" } else { "circuit" })?;
writeln!(f, " {} {{ ", self.identifier)?;
for field in self.members.iter() { for field in self.members.iter() {
writeln!(f, " {}", field)?; writeln!(f, " {}", field)?;
} }
@ -59,16 +71,4 @@ impl Circuit {
} }
} }
impl fmt::Debug for Circuit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Display for Circuit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
crate::simple_node_impl!(Circuit); crate::simple_node_impl!(Circuit);

View File

@ -57,7 +57,7 @@ pub enum Expression {
Binary(BinaryExpression), Binary(BinaryExpression),
/// A call expression, e.g., `my_fun(args)`. /// A call expression, e.g., `my_fun(args)`.
Call(CallExpression), Call(CallExpression),
/// An expression constructing a structure like `Foo { bar: 42, baz }`. /// An expression constructing a circuit like `Foo { bar: 42, baz }`.
CircuitInit(CircuitInitExpression), CircuitInit(CircuitInitExpression),
/// An expression of type "error". /// An expression of type "error".
/// Will result in a compile error eventually. /// Will result in a compile error eventually.

View File

@ -38,18 +38,16 @@ pub struct Program {
pub circuits: IndexMap<Identifier, Circuit>, pub circuits: IndexMap<Identifier, Circuit>,
} }
impl AsRef<Program> for Program {
fn as_ref(&self) -> &Program {
self
}
}
impl fmt::Display for Program { impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (_, function) in self.functions.iter() { for (_, function) in self.functions.iter() {
function.fmt(f)?; function.fmt(f)?;
writeln!(f,)?; writeln!(f,)?;
} }
for (_, circuit) in self.circuits.iter() {
circuit.fmt(f)?;
writeln!(f,)?;
}
write!(f, "") write!(f, "")
} }
} }
@ -66,12 +64,12 @@ impl Program {
} }
/// Extract the name of the program. /// Extract the name of the program.
pub fn get_name(&self) -> String { pub fn name(&self) -> &str {
self.name.to_string() &self.name
} }
/// Sets the name of the program. /// Sets the name of the program.
pub fn name(mut self, name: String) -> Self { pub fn set_name(mut self, name: String) -> Self {
self.name = name; self.name = name;
self self
} }

View File

@ -60,7 +60,7 @@ impl Type {
| (Type::Scalar, Type::Scalar) | (Type::Scalar, Type::Scalar)
| (Type::String, Type::String) => true, | (Type::String, Type::String) => true,
(Type::IntegerType(left), Type::IntegerType(right)) => left.eq(right), (Type::IntegerType(left), Type::IntegerType(right)) => left.eq(right),
(Type::Identifier(left), Type::Identifier(right)) => left.eq(right), (Type::Identifier(left), Type::Identifier(right)) => left.matches(right),
_ => false, _ => false,
} }
} }

View File

@ -16,7 +16,6 @@
use super::*; use super::*;
use leo_errors::{ParserError, Result}; use leo_errors::{ParserError, Result};
use leo_span::sym;
use snarkvm_dpc::{prelude::Address, testnet2::Testnet2}; use snarkvm_dpc::{prelude::Address, testnet2::Testnet2};
@ -537,17 +536,8 @@ impl ParserContext<'_> {
Token::Ident(name) => { Token::Ident(name) => {
let ident = Identifier { name, span }; let ident = Identifier { name, span };
if !self.disallow_circuit_construction && self.check(&Token::LeftCurly) { if !self.disallow_circuit_construction && self.check(&Token::LeftCurly) {
self.parse_circuit_expression(ident)? // Parse circuit and records inits as circuit expressions.
} else { // Enforce circuit or record type later at type checking.
Expression::Identifier(ident)
}
}
Token::SelfUpper => {
let ident = Identifier {
name: sym::SelfUpper,
span,
};
if !self.disallow_circuit_construction && self.check(&Token::LeftCurly) {
self.parse_circuit_expression(ident)? self.parse_circuit_expression(ident)?
} else { } else {
Expression::Identifier(ident) Expression::Identifier(ident)

View File

@ -27,7 +27,7 @@ impl ParserContext<'_> {
while self.has_next() { while self.has_next() {
match &self.token.token { match &self.token.token {
Token::Circuit => { Token::Circuit | Token::Record => {
let (id, circuit) = self.parse_circuit()?; let (id, circuit) = self.parse_circuit()?;
circuits.insert(id, circuit); circuits.insert(id, circuit);
} }
@ -36,12 +36,10 @@ impl ParserContext<'_> {
functions.insert(id, function); functions.insert(id, function);
} }
Token::Ident(sym::test) => return Err(ParserError::test_function(self.token.span).into()), Token::Ident(sym::test) => return Err(ParserError::test_function(self.token.span).into()),
// Const functions share the first token with the global Const.
Token::Function => { Token::Function => {
let (id, function) = self.parse_function()?; let (id, function) = self.parse_function()?;
functions.insert(id, function); functions.insert(id, function);
} }
_ => return Err(Self::unexpected_item(&self.token).into()), _ => return Err(Self::unexpected_item(&self.token).into()),
} }
} }
@ -65,9 +63,9 @@ impl ParserContext<'_> {
) )
} }
/// Returns a [`CircuitMember`] AST node if the next tokens represent a circuit member variable /// Returns a [`Vec<CircuitMember>`] AST node if the next tokens represent a circuit member variable
/// or circuit member function or circuit member constant. /// or circuit member function or circuit member constant.
pub fn parse_circuit_declaration(&mut self) -> Result<(Vec<CircuitMember>, Span)> { pub fn parse_circuit_members(&mut self) -> Result<(Vec<CircuitMember>, Span)> {
let mut members = Vec::new(); let mut members = Vec::new();
let (mut semi_colons, mut commas) = (false, false); let (mut semi_colons, mut commas) = (false, false);
@ -106,7 +104,7 @@ impl ParserContext<'_> {
} }
/// Parses `IDENT: TYPE`. /// Parses `IDENT: TYPE`.
fn parse_typed_field_name(&mut self) -> Result<(Identifier, Type)> { fn parse_member(&mut self) -> Result<(Identifier, Type)> {
let name = self.expect_ident()?; let name = self.expect_ident()?;
self.expect(&Token::Colon)?; self.expect(&Token::Colon)?;
let type_ = self.parse_all_types()?.0; let type_ = self.parse_all_types()?.0;
@ -120,7 +118,7 @@ impl ParserContext<'_> {
self.expect(&Token::Const)?; self.expect(&Token::Const)?;
// `IDENT: TYPE = EXPR`: // `IDENT: TYPE = EXPR`:
let (_name, _type_) = self.parse_typed_field_name()?; let (_name, _type_) = self.parse_member()?;
self.expect(&Token::Assign)?; self.expect(&Token::Assign)?;
let expr = self.parse_expression()?; let expr = self.parse_expression()?;
@ -134,7 +132,7 @@ impl ParserContext<'_> {
/// Returns a [`CircuitMember`] AST node if the next tokens represent a circuit member variable. /// Returns a [`CircuitMember`] AST node if the next tokens represent a circuit member variable.
pub fn parse_member_variable_declaration(&mut self) -> Result<CircuitMember> { pub fn parse_member_variable_declaration(&mut self) -> Result<CircuitMember> {
let (name, type_) = self.parse_typed_field_name()?; let (name, type_) = self.parse_member()?;
Ok(CircuitMember::CircuitVariable(name, type_)) Ok(CircuitMember::CircuitVariable(name, type_))
} }
@ -145,26 +143,28 @@ impl ParserContext<'_> {
// CAUTION: function members are unstable for testnet3. // CAUTION: function members are unstable for testnet3.
let function = self.parse_function()?; let function = self.parse_function()?;
return Err(ParserError::circuit_functions_unstable(function.1.span()).into()); Err(ParserError::circuit_functions_unstable(function.1.span()).into())
// Ok(CircuitMember::CircuitFunction(Box::new(function.1))) // Ok(CircuitMember::CircuitFunction(Box::new(function.1)))
} else { } else {
return Err(Self::unexpected_item(&self.token).into()); Err(Self::unexpected_item(&self.token).into())
} }
} }
/// Returns an [`(Identifier, Function)`] ast node if the next tokens represent a circuit declaration. /// Parses a circuit or record definition, e.g., `circit Foo { ... }` or `record Foo { ... }`.
pub(super) fn parse_circuit(&mut self) -> Result<(Identifier, Circuit)> { pub(super) fn parse_circuit(&mut self) -> Result<(Identifier, Circuit)> {
let start = self.expect(&Token::Circuit)?; let is_record = matches!(&self.token.token, Token::Record);
let start = self.expect_any(&[Token::Circuit, Token::Record])?;
let circuit_name = self.expect_ident()?; let circuit_name = self.expect_ident()?;
self.expect(&Token::LeftCurly)?; self.expect(&Token::LeftCurly)?;
let (members, end) = self.parse_circuit_declaration()?; let (members, end) = self.parse_circuit_members()?;
Ok(( Ok((
circuit_name.clone(), circuit_name,
Circuit { Circuit {
identifier: circuit_name, identifier: circuit_name,
members, members,
is_record,
span: start + end, span: start + end,
}, },
)) ))

View File

@ -316,6 +316,7 @@ impl Token {
"in" => Token::In, "in" => Token::In,
"let" => Token::Let, "let" => Token::Let,
"public" => Token::Public, "public" => Token::Public,
"record" => Token::Record,
"return" => Token::Return, "return" => Token::Return,
"scalar" => Token::Scalar, "scalar" => Token::Scalar,
"string" => Token::String, "string" => Token::String,

View File

@ -89,7 +89,7 @@ pub enum Token {
U32, U32,
U64, U64,
U128, U128,
SelfUpper, Record,
// Regular Keywords // Regular Keywords
Circuit, Circuit,
@ -140,9 +140,9 @@ pub const KEYWORD_TOKENS: &[Token] = &[
Token::In, Token::In,
Token::Let, Token::Let,
Token::Public, Token::Public,
Token::Record,
Token::Return, Token::Return,
Token::SelfLower, Token::SelfLower,
Token::SelfUpper,
Token::Scalar, Token::Scalar,
Token::Static, Token::Static,
Token::String, Token::String,
@ -184,10 +184,10 @@ impl Token {
Token::In => sym::In, Token::In => sym::In,
Token::Let => sym::Let, Token::Let => sym::Let,
Token::Public => sym::Public, Token::Public => sym::Public,
Token::Record => sym::record,
Token::Return => sym::Return, Token::Return => sym::Return,
Token::Scalar => sym::scalar, Token::Scalar => sym::scalar,
Token::SelfLower => sym::SelfLower, Token::SelfLower => sym::SelfLower,
Token::SelfUpper => sym::SelfUpper,
Token::Static => sym::Static, Token::Static => sym::Static,
Token::String => sym::string, Token::String => sym::string,
Token::True => sym::True, Token::True => sym::True,
@ -267,7 +267,7 @@ impl fmt::Display for Token {
U32 => write!(f, "u32"), U32 => write!(f, "u32"),
U64 => write!(f, "u64"), U64 => write!(f, "u64"),
U128 => write!(f, "u128"), U128 => write!(f, "u128"),
SelfUpper => write!(f, "Self"), Record => write!(f, "record"),
Circuit => write!(f, "circuit"), Circuit => write!(f, "circuit"),
Console => write!(f, "console"), Console => write!(f, "console"),

View File

@ -58,9 +58,14 @@ impl<'a> SymbolTable<'a> {
} }
pub fn insert_circuit(&mut self, symbol: Symbol, insert: &'a Circuit) -> Result<()> { pub fn insert_circuit(&mut self, symbol: Symbol, insert: &'a Circuit) -> Result<()> {
if self.circuits.contains_key(&symbol) { if let Some(existing) = self.circuits.get(&symbol) {
// Return an error if the circuit name has already been inserted. // Error if the circuit or record already exists.
return Err(AstError::shadowed_circuit(symbol, insert.span).into()); let err = if existing.is_record {
AstError::shadowed_record(symbol, insert.span).into()
} else {
AstError::shadowed_circuit(symbol, insert.span).into()
};
return Err(err);
} }
self.circuits.insert(symbol, insert); self.circuits.insert(symbol, insert);
Ok(()) Ok(())
@ -99,7 +104,7 @@ impl<'a> SymbolTable<'a> {
} }
} }
impl<'a> Display for SymbolTable<'a> { impl Display for SymbolTable<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SymbolTable")?; write!(f, "SymbolTable")?;

View File

@ -528,20 +528,20 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
} }
// Check circuit member types. // Check circuit member types.
circ.members.iter().for_each(|expected| match expected { circ.members
CircuitMember::CircuitVariable(name, type_) => { .iter()
.for_each(|CircuitMember::CircuitVariable(name, ty)| {
// Lookup circuit variable name. // Lookup circuit variable name.
if let Some(actual) = input.members.iter().find(|member| member.identifier.name == name.name) { if let Some(actual) = input.members.iter().find(|member| member.identifier.name == name.name) {
if let Some(expr) = &actual.expression { if let Some(expr) = &actual.expression {
self.visit_expression(expr, &Some(*type_)); self.visit_expression(expr, &Some(*ty));
} }
} else { } else {
self.handler.emit_err( self.handler.emit_err(
TypeCheckerError::unknown_sym("circuit member variable", name, name.span()).into(), TypeCheckerError::unknown_sym("circuit member variable", name, name.span()).into(),
); );
}; };
} });
});
Some(ret) Some(ret)
} else { } else {

View File

@ -18,6 +18,7 @@ use crate::{Declaration, TypeChecker, VariableSymbol};
use leo_ast::*; use leo_ast::*;
use leo_errors::TypeCheckerError; use leo_errors::TypeCheckerError;
use leo_span::sym;
use std::collections::HashSet; use std::collections::HashSet;
impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
@ -53,8 +54,32 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
// Check for conflicting circuit member names. // Check for conflicting circuit member names.
let mut used = HashSet::new(); let mut used = HashSet::new();
if !input.members.iter().all(|member| used.insert(member.name())) { if !input.members.iter().all(|member| used.insert(member.name())) {
self.handler self.handler.emit_err(if input.is_record {
.emit_err(TypeCheckerError::duplicate_circuit_member(input.name(), input.span()).into()); TypeCheckerError::duplicate_record_variable(input.name(), input.span()).into()
} else {
TypeCheckerError::duplicate_circuit_member(input.name(), input.span()).into()
});
}
// For records, enforce presence of `owner: Address` and `balance: u64` members.
if input.is_record {
let check_has_field = |need, expected_ty: Type| match input
.members
.iter()
.find_map(|CircuitMember::CircuitVariable(v, t)| (v.name == need).then(|| (v, t)))
{
Some((_, actual_ty)) if expected_ty.eq_flat(actual_ty) => {} // All good, found + right type!
Some((field, _)) => {
self.handler
.emit_err(TypeCheckerError::record_var_wrong_type(field, expected_ty, input.span()).into());
}
None => {
self.handler
.emit_err(TypeCheckerError::required_record_variable(need, expected_ty, input.span()).into());
}
};
check_has_field(sym::owner, Type::Address);
check_has_field(sym::balance, Type::IntegerType(IntegerType::U64));
} }
} }
} }

View File

@ -103,12 +103,16 @@ impl<'a> TypeChecker<'a> {
} }
} }
/// Emits a type checker error.
pub(crate) fn emit_err(&self, err: TypeCheckerError) {
self.handler.emit_err(err.into());
}
/// Emits an error if the given type conflicts with a core library type. /// Emits an error if the given type conflicts with a core library type.
pub(crate) fn check_ident_type(&self, type_: &Option<Type>) { pub(crate) fn check_ident_type(&self, type_: &Option<Type>) {
if let Some(Type::Identifier(ident)) = type_ { if let Some(Type::Identifier(ident)) = type_ {
if self.account_types.contains(&ident.name) || self.algorithms_types.contains(&ident.name) { if self.account_types.contains(&ident.name) || self.algorithms_types.contains(&ident.name) {
self.handler self.emit_err(TypeCheckerError::core_type_name_conflict(&ident.name, ident.span()));
.emit_err(TypeCheckerError::core_type_name_conflict(&ident.name, ident.span()).into());
} }
} }
} }
@ -121,9 +125,11 @@ impl<'a> TypeChecker<'a> {
match CoreInstruction::from_symbols(ident.name, function.name) { match CoreInstruction::from_symbols(ident.name, function.name) {
None => { None => {
// Not a core library circuit. // Not a core library circuit.
self.handler.emit_err( self.emit_err(TypeCheckerError::invalid_core_instruction(
TypeCheckerError::invalid_core_instruction(&ident.name, function.name, ident.span()).into(), &ident.name,
); function.name,
ident.span(),
));
} }
Some(core_circuit) => return Some(core_circuit), Some(core_circuit) => return Some(core_circuit),
} }
@ -134,12 +140,10 @@ impl<'a> TypeChecker<'a> {
/// Emits an error if the two given types are not equal. /// Emits an error if the two given types are not equal.
pub(crate) fn assert_eq_types(&self, t1: Option<Type>, t2: Option<Type>, span: Span) { pub(crate) fn assert_eq_types(&self, t1: Option<Type>, t2: Option<Type>, span: Span) {
match (t1, t2) { match (t1, t2) {
(Some(t1), Some(t2)) if t1 != t2 => self (Some(t1), Some(t2)) if t1 != t2 => self.emit_err(TypeCheckerError::type_should_be(t1, t2, span)),
.handler (Some(type_), None) | (None, Some(type_)) => {
.emit_err(TypeCheckerError::type_should_be(t1, t2, span).into()), self.emit_err(TypeCheckerError::type_should_be("no type", type_, span))
(Some(type_), None) | (None, Some(type_)) => self }
.handler
.emit_err(TypeCheckerError::type_should_be("no type", type_, span).into()),
_ => {} _ => {}
} }
} }
@ -147,9 +151,8 @@ impl<'a> TypeChecker<'a> {
/// Returns the `circuit` type and emits an error if the `expected` type does not match. /// Returns the `circuit` type and emits an error if the `expected` type does not match.
pub(crate) fn assert_expected_circuit(&mut self, circuit: Identifier, expected: &Option<Type>, span: Span) -> Type { pub(crate) fn assert_expected_circuit(&mut self, circuit: Identifier, expected: &Option<Type>, span: Span) -> Type {
if let Some(Type::Identifier(expected)) = expected { if let Some(Type::Identifier(expected)) = expected {
if expected.name != circuit.name { if !circuit.matches(expected) {
self.handler self.emit_err(TypeCheckerError::type_should_be(circuit.name, expected.name, span));
.emit_err(TypeCheckerError::type_should_be(circuit.name, expected.name, span).into());
} }
} }
@ -159,9 +162,8 @@ impl<'a> TypeChecker<'a> {
/// Returns the given `actual` type and emits an error if the `expected` type does not match. /// Returns the given `actual` type and emits an error if the `expected` type does not match.
pub(crate) fn assert_expected_option(&mut self, actual: Type, expected: &Option<Type>, span: Span) -> Type { pub(crate) fn assert_expected_option(&mut self, actual: Type, expected: &Option<Type>, span: Span) -> Type {
if let Some(expected) = expected { if let Some(expected) = expected {
if &actual != expected { if !actual.eq_flat(expected) {
self.handler self.emit_err(TypeCheckerError::type_should_be(actual, expected, span));
.emit_err(TypeCheckerError::type_should_be(actual, expected, span).into());
} }
} }
@ -172,9 +174,8 @@ impl<'a> TypeChecker<'a> {
/// `span` should be the location of the expected type. /// `span` should be the location of the expected type.
pub(crate) fn assert_expected_type(&mut self, actual: &Option<Type>, expected: Type, span: Span) -> Type { pub(crate) fn assert_expected_type(&mut self, actual: &Option<Type>, expected: Type, span: Span) -> Type {
if let Some(actual) = actual { if let Some(actual) = actual {
if actual != &expected { if !actual.eq_flat(&expected) {
self.handler self.emit_err(TypeCheckerError::type_should_be(actual, expected, span));
.emit_err(TypeCheckerError::type_should_be(actual, expected, span).into());
} }
} }
@ -185,14 +186,11 @@ impl<'a> TypeChecker<'a> {
pub(crate) fn assert_one_of_types(&self, type_: &Option<Type>, expected: &[Type], span: Span) { pub(crate) fn assert_one_of_types(&self, type_: &Option<Type>, expected: &[Type], span: Span) {
if let Some(type_) = type_ { if let Some(type_) = type_ {
if !expected.iter().any(|t: &Type| t == type_) { if !expected.iter().any(|t: &Type| t == type_) {
self.handler.emit_err( self.emit_err(TypeCheckerError::expected_one_type_of(
TypeCheckerError::expected_one_type_of( expected.iter().map(|t| t.to_string() + ",").collect::<String>(),
expected.iter().map(|t| t.to_string() + ",").collect::<String>(), type_,
type_, span,
span, ));
)
.into(),
);
} }
} }
} }

View File

@ -96,6 +96,7 @@ keyword = %s"address"
/ %s"in" / %s"in"
/ %s"let" / %s"let"
/ %s"public" / %s"public"
/ %s"record"
/ %s"return" / %s"return"
/ %s"scalar" / %s"scalar"
/ %s"string" / %s"string"
@ -395,8 +396,13 @@ circuit-declaration = %s"circuit" "{" circuit-component-declaration
circuit-component-declaration = identifier ":" type circuit-component-declaration = identifier ":" type
record-declaration = %s"record" "{" circuit-component-declaration
*( "," circuit-component-declaration )
[ "," ] "}"
declaration = function-declaration declaration = function-declaration
/ circuit-declaration / circuit-declaration
/ record-declaration
file = *declaration file = *declaration

View File

@ -1,8 +1,23 @@
circuit Foo { record Token {
x: u8, // The token owner.
y: u8, owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
} }
function main(a: u8) -> field { function mint(owner: address, amount: u64) -> Token {
return BHP256::hash(a); return Token {
owner,
balance: 0u64,
amount,
};
}
function main(x: address) -> u64 {
const c: u64 = 1u64;
let t: Token = mint(x, c);
return t.balance;
} }

View File

@ -1,6 +1,6 @@
// CAUTION: Work in progress // CAUTION: Work in progress
circuit Token { record Token {
// The token owner. // The token owner.
owner: address, owner: address,
// The Aleo balance (in gates). // The Aleo balance (in gates).
@ -42,7 +42,7 @@ function transfer(r0: Token, r1: Receiver) -> Token {
let r4: Token = mint(r0.owner, r0.amount); let r4: Token = mint(r0.owner, r0.amount);
// return (r3, r4); // return (r3, r4);
return r3 return r3;
} }
function main() -> u8 { function main() -> u8 {

View File

@ -155,6 +155,14 @@ create_messages!(
help: None, help: None,
} }
/// For when a user shadows a record.
@formatted
shadowed_record {
args: (record: impl Display),
msg: format!("record `{record}` shadowed by"),
help: None,
}
/// For when a user shadows a variable. /// For when a user shadows a variable.
@formatted @formatted
shadowed_variable { shadowed_variable {

View File

@ -191,6 +191,16 @@ create_messages!(
help: None, help: None,
} }
/// For when the user tries initialize a circuit with the incorrect number of args.
@formatted
incorrect_num_record_variables {
args: (expected: impl Display, received: impl Display),
msg: format!(
"Record expected `{expected}` variables, but got `{received}`",
),
help: None,
}
/// An invalid access call is made e.g., `bool::MAX` /// An invalid access call is made e.g., `bool::MAX`
@formatted @formatted
invalid_access_expression { invalid_access_expression {
@ -211,6 +221,16 @@ create_messages!(
help: None, help: None,
} }
/// Attempted to define more that one record variable with the same name.
@formatted
duplicate_record_variable {
args: (record: impl Display),
msg: format!(
"Record {record} defined with more than one variable with the same name."
),
help: None,
}
/// Attempted to access an invalid circuit. /// Attempted to access an invalid circuit.
@formatted @formatted
invalid_circuit { invalid_circuit {
@ -230,4 +250,18 @@ create_messages!(
), ),
help: None, help: None,
} }
@formatted
required_record_variable {
args: (name: impl Display, type_: impl Display),
msg: format!("The `record` type requires the variable `{name}: {type_}`."),
help: None,
}
@formatted
record_var_wrong_type {
args: (name: impl Display, type_: impl Display),
msg: format!("The field `{name}` in a `record` must have type `{type_}`."),
help: None,
}
); );

View File

@ -16,7 +16,6 @@
// Copyright Rust project developers under MIT or APACHE-2.0. // Copyright Rust project developers under MIT or APACHE-2.0.
use core::alloc::Layout; use core::alloc::Layout;
use core::cell::{Cell, RefCell}; use core::cell::{Cell, RefCell};
use core::mem::{self, MaybeUninit}; use core::mem::{self, MaybeUninit};

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
//! Defines the `Span` type used to track where code comes from. //! Defines the `Span` type used to track where code comes from.
use core::ops::{Add, Sub}; use core::ops::{Add, Sub};

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::dropless::DroplessArena; use crate::dropless::DroplessArena;
use crate::source_map::SourceMap; use crate::source_map::SourceMap;
@ -165,6 +164,7 @@ symbols! {
i32, i32,
i64, i64,
i128, i128,
record,
scalar, scalar,
string, string,
u8, u8,
@ -208,6 +208,12 @@ symbols! {
test, test,
Type: "type", Type: "type",
public,
private,
owner,
balance,
// todo: remove these.
CONTAINER_PSEUDO_CIRCUIT: "$InputContainer", CONTAINER_PSEUDO_CIRCUIT: "$InputContainer",
REGISTERS_PSEUDO_CIRCUIT: "$InputRegister", REGISTERS_PSEUDO_CIRCUIT: "$InputRegister",
RECORD_PSEUDO_CIRCUIT: "$InputRecord", RECORD_PSEUDO_CIRCUIT: "$InputRecord",
@ -216,11 +222,8 @@ symbols! {
// input file // input file
registers, registers,
record,
state, state,
state_leaf, state_leaf,
public,
private,
} }
/// An interned string. /// An interned string.

View File

@ -0,0 +1,14 @@
/*
namespace: Compile
expectation: Fail
*/
// This record does define the `balance` variable but with the wrong type.
record Token {
balance: address,
owner: address,
}
function main() -> bool {
return true;
}

View File

@ -0,0 +1,17 @@
/*
namespace: Compile
expectation: Pass
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function main() -> bool {
return true;
}

View File

@ -0,0 +1,21 @@
/*
namespace: Compile
expectation: Fail
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
circuit Token { // This circuit cannot have the same name as the record defined above it.
x: u32,
}
function main() -> bool {
return true;
}

View File

@ -0,0 +1,19 @@
/*
namespace: Compile
expectation: Fail
*/
record Token {
// The token owner.
owner: address,
// The token owner.
owner: address, // Cannot define two record variables with the same name.
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function main() -> bool {
return true;
}

View File

@ -0,0 +1,28 @@
/*
namespace: Compile
expectation: Pass
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function mint(r0: address, r1: u64) -> Token {
return Token {
owner: r0,
balance: 0u64,
amount: r1,
};
}
function main(x: address) -> u64 {
const c: u64 = 1u64;
let t: Token = mint(x, c);
return t.balance;
}

View File

@ -0,0 +1,28 @@
/*
namespace: Compile
expectation: Pass
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function mint(owner: address, amount: u64) -> Token {
return Token {
owner,
balance: 0u64,
amount,
};
}
function main(x: address) -> u64 {
const c: u64 = 1u64;
let t: Token = mint(x, c);
return t.balance;
}

View File

@ -0,0 +1,28 @@
/*
namespace: Compile
expectation: Fail
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function mint(r0: address, r1: u64) -> Token {
return Token {
owner: r1, // This variable should be type address.
balance: 0u64,
amount: r0, // This variable should be type u64.
};
}
function main(x: address) -> u64 {
const c: u64 = 1u64;
let t: Token = mint(x, c);
return t.balance;
}

View File

@ -0,0 +1,28 @@
/*
namespace: Compile
expectation: Fail
*/
record Token {
// The token owner.
owner: address,
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function mint(r0: address, r1: u64) -> Token {
return Token {
sender: r0, // This variable should be named `owner`.
balance: 0u64,
amount: r1,
};
}
function main(x: address) -> u64 {
const c: u64 = 1u64;
let t: Token = mint(x, c);
return t.balance;
}

View File

View File

@ -0,0 +1,16 @@
/*
namespace: Compile
expectation: Fail
*/
// This record does not define the `owner` variable required for a record type.
record Token {
// The Aleo balance (in gates).
balance: u64,
// The token amount.
amount: u64,
}
function main() -> bool {
return true;
}

View File

@ -0,0 +1,14 @@
/*
namespace: Compile
expectation: Fail
*/
// This record does define the `owner` variable but with the wrong type.
record Token {
balance: u64,
owner: bool,
}
function main() -> bool {
return true;
}

View File

@ -2,4 +2,4 @@
namespace: Compile namespace: Compile
expectation: Fail expectation: Fail
outputs: outputs:
- "Error [ETYC0372018]: Circuit Bar defined with more than one member with the same name.\n --> compiler-test:3:1\n |\n 3 | circuit Bar {\n 4 | x: u32,\n 5 | x: u32,\n 6 | }\n | ^\n" - "Error [ETYC0372019]: Circuit Bar defined with more than one member with the same name.\n --> compiler-test:3:1\n |\n 3 | circuit Bar {\n 4 | x: u32,\n 5 | x: u32,\n 6 | }\n | ^\n"

View File

@ -4,5 +4,5 @@ expectation: Pass
outputs: outputs:
- output: - output:
- initial_input_ast: acb54d555ee391d519919827f5949bf1600d18004ce461097d062f91108563ba - initial_input_ast: acb54d555ee391d519919827f5949bf1600d18004ce461097d062f91108563ba
initial_ast: 347eb4d4a124a759627e26bad6ea283b07b7bc07ab35dc1576faf699c3d91e3d initial_ast: 2fd04130d93c29b06f1288c682dd1ce37731ef0762f8e8d70d994aa97fa55c1e
symbol_table: d522662a21597d6d4b9ca630498b48f40ad05fb289080451879c90c3530de28b symbol_table: d522662a21597d6d4b9ca630498b48f40ad05fb289080451879c90c3530de28b

View File

@ -4,5 +4,5 @@ expectation: Pass
outputs: outputs:
- output: - output:
- initial_input_ast: no input - initial_input_ast: no input
initial_ast: c7a7ae897a82a174983ec9e81f0bcc11328fc55ea24111523131e345a363d9b8 initial_ast: 8507cd1753f4d2a835fa34d3d784487d89d595ea415d51145dd7291a839159c2
symbol_table: e6f85704fccd0ca0f0461ae54cb604159a5f41a2175aad6b76bd534166f1bc6b symbol_table: e6f85704fccd0ca0f0461ae54cb604159a5f41a2175aad6b76bd534166f1bc6b

View File

@ -4,5 +4,5 @@ expectation: Pass
outputs: outputs:
- output: - output:
- initial_input_ast: no input - initial_input_ast: no input
initial_ast: 7c16a20b88d661dab8e581d47998252fc0bd2a93199f8b4945c924ade5ebae36 initial_ast: 9489ab9b78bd31ac516e1674a83c6b35708238174df88374a7b19cef3d4a8e8a
symbol_table: 9eeb9c327aa691ac9126681fa2d4be149bb69621d30ac30a1432b52e42b56307 symbol_table: 9eeb9c327aa691ac9126681fa2d4be149bb69621d30ac30a1432b52e42b56307

View File

@ -4,5 +4,5 @@ expectation: Pass
outputs: outputs:
- output: - output:
- initial_input_ast: 29f6139d908d390f890f04d8ee620757d29b7f71cd48c46ff65bc1e70aae840c - initial_input_ast: 29f6139d908d390f890f04d8ee620757d29b7f71cd48c46ff65bc1e70aae840c
initial_ast: 6e1af09bb13f26f60786c16f66286caffa646eae44511c08c2f6be46d8a1a3d1 initial_ast: 630995cc22fb6ec613f02e3aaa18392770158b2bbaf5aa1736c0bf71dd7357ce
symbol_table: 1e3d03c1d2a087812fc00dd4c1b48174df13bc5278fd65c104c9a904644061db symbol_table: 1e3d03c1d2a087812fc00dd4c1b48174df13bc5278fd65c104c9a904644061db

View File

@ -2,4 +2,4 @@
namespace: Compile namespace: Compile
expectation: Fail expectation: Fail
outputs: outputs:
- "Error [ETYC0372014]: The type Record is a reserved core type name.\n --> compiler-test:4:30\n |\n 4 | function main(public record: Record, a: bool) -> bool {\n | ^^^^^^\n" - "Error [EPAR0370009]: unexpected string: expected 'ident', got 'record'\n --> compiler-test:4:22\n |\n 4 | function main(public record: Record, a: bool) -> bool {\n | ^^^^^^"

View File

@ -2,4 +2,4 @@
namespace: Compile namespace: Compile
expectation: Fail expectation: Fail
outputs: outputs:
- "Error [EAST0372016]: variable `a` shadowed by\n --> compiler-test:3:23\n |\n 3 | function main(a: u32, a: u32) -> u32 {\n | ^\n" - "Error [EAST0372017]: variable `a` shadowed by\n --> compiler-test:3:23\n |\n 3 | function main(a: u32, a: u32) -> u32 {\n | ^\n"

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372024]: The field `balance` in a `record` must have type `u64`.\n --> compiler-test:4:1\n |\n 4 | record Token {\n 5 | balance: address,\n 6 | owner: address,\n 7 | }\n | ^\n"

View File

@ -0,0 +1,8 @@
---
namespace: Compile
expectation: Pass
outputs:
- output:
- initial_input_ast: no input
initial_ast: ed6dbb2a60da9a91da4b3845e3919b0520666cf4d7223e5634e1e8e38dd9243d
symbol_table: c49906bcded430e36886bfabc35c5740e4657ac82761b80b871f6d19ec6d9dda

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [EAST0372016]: record `Token` shadowed by\n --> compiler-test:12:1\n |\n 12 | circuit Token { // This circuit cannot have the same name as the record defined above it.\n 13 | x: u32,\n 14 | }\n | ^\n"

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372020]: Record Token defined with more than one variable with the same name.\n --> compiler-test:3:1\n |\n 3 | record Token {\n 4 | // The token owner.\n 5 | owner: address,\n 6 | // The token owner.\n 7 | owner: address, // Cannot define two record variables with the same name.\n 8 | // The Aleo balance (in gates).\n 9 | balance: u64,\n 10 | // The token amount.\n 11 | amount: u64,\n 12 | }\n | ^\n"

View File

@ -0,0 +1,8 @@
---
namespace: Compile
expectation: Pass
outputs:
- output:
- initial_input_ast: no input
initial_ast: cc9fdc5ee476d5c8930260c5fc50c968915434892180f0084f15cd69b905dc20
symbol_table: 926c2f494fbb7914574e7b95bedd8992eaf028143e19bebcdcdf474fcb5eb1c5

View File

@ -0,0 +1,8 @@
---
namespace: Compile
expectation: Pass
outputs:
- output:
- initial_input_ast: no input
initial_ast: c765de9e29d4ca9bd9ba2f7a5ee72c2e4c8278948d32a6c9a441f5eacde564ea
symbol_table: de1844db50840db6655f51a2903da4287d51c03a6e693843bdd6be95c6d627f8

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372003]: Found type `u64` but type `address` was expected\n --> compiler-test:12:28\n |\n 12 | function mint(r0: address, r1: u64) -> Token {\n | ^^\nError [ETYC0372003]: Found type `address` but type `u64` was expected\n --> compiler-test:12:15\n |\n 12 | function mint(r0: address, r1: u64) -> Token {\n | ^^\n"

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372005]: Unknown circuit member variable `owner`\n --> compiler-test:5:5\n |\n 5 | owner: address,\n | ^^^^^\n"

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372023]: The `record` type requires the variable `owner: address`.\n --> compiler-test:4:1\n |\n 4 | record Token {\n 5 | // The Aleo balance (in gates).\n 6 | balance: u64,\n 7 | // The token amount.\n 8 | amount: u64,\n 9 | }\n | ^\n"

View File

@ -0,0 +1,5 @@
---
namespace: Compile
expectation: Fail
outputs:
- "Error [ETYC0372024]: The field `owner` in a `record` must have type `address`.\n --> compiler-test:4:1\n |\n 4 | record Token {\n 5 | balance: u64,\n 6 | owner: bool,\n 7 | }\n | ^\n"

View File

@ -2,4 +2,4 @@
namespace: Compile namespace: Compile
expectation: Fail expectation: Fail
outputs: outputs:
- "Error [EAST0372016]: variable `x` shadowed by\n --> compiler-test:5:4\n |\n 5 | \tlet x: bool = true;\n | ^^^^^^^^^^^^^^^^^^\n" - "Error [EAST0372017]: variable `x` shadowed by\n --> compiler-test:5:4\n |\n 5 | \tlet x: bool = true;\n | ^^^^^^^^^^^^^^^^^^\n"