Merge pull request #1786 from AleoHQ/feature/type-checking

[Impl] type-checking
This commit is contained in:
Collin Chin 2022-05-10 11:17:44 -07:00 committed by GitHub
commit be9fa96d9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
541 changed files with 2790 additions and 796 deletions

View File

@ -14,6 +14,7 @@
// 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 leo_errors::Result;
use leo_span::{Span, Symbol}; use leo_span::{Span, Symbol};
use crate::Node; use crate::Node;

View File

@ -47,20 +47,6 @@ pub enum BinaryOperation {
Le, Le,
/// Lesser-than relation, i.e. `<`. /// Lesser-than relation, i.e. `<`.
Lt, Lt,
/// Bitwise-or inclusive, i.e., `|`.
BitOr,
/// Bitwise-and, i.e., `&`.
BitAnd,
/// Bitwise-or exclusive, i.e., `^`.
BitXor,
/// Shift-right, i.e `>>`.
Shr,
/// Unsigned shift-right, i.e `>>>`.
ShrSigned,
/// Shift-left, i.e `<<`.
Shl,
/// Modulus or remainder operation, i.e., `%`.
Mod,
} }
/// The category a binary operation belongs to. /// The category a binary operation belongs to.
@ -88,13 +74,6 @@ impl AsRef<str> for BinaryOperation {
BinaryOperation::Gt => ">", BinaryOperation::Gt => ">",
BinaryOperation::Le => "<=", BinaryOperation::Le => "<=",
BinaryOperation::Lt => "<", BinaryOperation::Lt => "<",
BinaryOperation::BitOr => "|",
BinaryOperation::BitAnd => "&",
BinaryOperation::BitXor => "^",
BinaryOperation::Shr => ">>",
BinaryOperation::ShrSigned => ">>>",
BinaryOperation::Shl => "<<",
BinaryOperation::Mod => "%",
} }
} }
} }
@ -108,13 +87,6 @@ impl BinaryOperation {
| BinaryOperation::Sub | BinaryOperation::Sub
| BinaryOperation::Mul | BinaryOperation::Mul
| BinaryOperation::Div | BinaryOperation::Div
| BinaryOperation::BitOr
| BinaryOperation::BitAnd
| BinaryOperation::BitXor
| BinaryOperation::Shr
| BinaryOperation::ShrSigned
| BinaryOperation::Shl
| BinaryOperation::Mod
| BinaryOperation::Pow => BinaryOperationClass::Numeric, | BinaryOperation::Pow => BinaryOperationClass::Numeric,
BinaryOperation::Or BinaryOperation::Or
| BinaryOperation::And | BinaryOperation::And

View File

@ -15,7 +15,6 @@
// 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::{GroupValue, Identifier, IntegerType, Node}; use crate::{GroupValue, Identifier, IntegerType, Node};
use leo_span::Span; use leo_span::Span;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -24,9 +24,6 @@ pub enum UnaryOperation {
Not, Not,
/// The arithmetic negation operator, i.e., `-`. /// The arithmetic negation operator, i.e., `-`.
Negate, Negate,
/// The bitwise negation operator, i.e., `~`.
/// For example, it transforms `1010` to `0101`.
BitNot,
} }
impl AsRef<str> for UnaryOperation { impl AsRef<str> for UnaryOperation {
@ -34,7 +31,6 @@ impl AsRef<str> for UnaryOperation {
match self { match self {
UnaryOperation::Not => "!", UnaryOperation::Not => "!",
UnaryOperation::Negate => "-", UnaryOperation::Negate => "-",
UnaryOperation::BitNot => "~",
} }
} }
} }

View File

@ -33,8 +33,6 @@ pub enum ValueExpression {
/// A group literal, either product or affine. /// A group literal, either product or affine.
/// For example, `42group` or `(12, 52)group`. /// For example, `42group` or `(12, 52)group`.
Group(Box<GroupValue>), Group(Box<GroupValue>),
/// A negated non-integer literal, e.g., `-4.2`.
Implicit(String, #[serde(with = "leo_span::span_json")] Span),
/// An integer literal, e.g., `42`. /// An integer literal, e.g., `42`.
Integer(IntegerType, String, #[serde(with = "leo_span::span_json")] Span), Integer(IntegerType, String, #[serde(with = "leo_span::span_json")] Span),
/// A string literal, e.g., `"foobar"`. /// A string literal, e.g., `"foobar"`.
@ -49,7 +47,6 @@ impl fmt::Display for ValueExpression {
Boolean(boolean, _) => write!(f, "{}", boolean), Boolean(boolean, _) => write!(f, "{}", boolean),
Char(character) => write!(f, "{}", character), Char(character) => write!(f, "{}", character),
Field(field, _) => write!(f, "{}", field), Field(field, _) => write!(f, "{}", field),
Implicit(implicit, _) => write!(f, "{}", implicit),
Integer(type_, value, _) => write!(f, "{}{}", value, type_), Integer(type_, value, _) => write!(f, "{}{}", value, type_),
Group(group) => write!(f, "{}", group), Group(group) => write!(f, "{}", group),
String(string, _) => { String(string, _) => {
@ -66,12 +63,7 @@ impl Node for ValueExpression {
fn span(&self) -> &Span { fn span(&self) -> &Span {
use ValueExpression::*; use ValueExpression::*;
match &self { match &self {
Address(_, span) Address(_, span) | Boolean(_, span) | Field(_, span) | Integer(_, _, span) | String(_, span) => span,
| Boolean(_, span)
| Field(_, span)
| Implicit(_, span)
| Integer(_, _, span)
| String(_, span) => span,
Char(character) => &character.span, Char(character) => &character.span,
Group(group) => match &**group { Group(group) => match &**group {
GroupValue::Single(_, span) => span, GroupValue::Single(_, span) => span,
@ -83,12 +75,9 @@ impl Node for ValueExpression {
fn set_span(&mut self, new_span: Span) { fn set_span(&mut self, new_span: Span) {
use ValueExpression::*; use ValueExpression::*;
match self { match self {
Address(_, span) Address(_, span) | Boolean(_, span) | Field(_, span) | Integer(_, _, span) | String(_, span) => {
| Boolean(_, span) *span = new_span
| Field(_, span) }
| Implicit(_, span)
| Integer(_, _, span)
| String(_, span) => *span = new_span,
Char(character) => character.span = new_span, Char(character) => character.span = new_span,
Group(group) => match &mut **group { Group(group) => match &mut **group {
GroupValue::Single(_, span) => *span = new_span, GroupValue::Single(_, span) => *span = new_span,

View File

@ -47,7 +47,7 @@ pub struct FunctionInputVariable {
/// The mode of the function parameter. /// The mode of the function parameter.
mode: ParamMode, mode: ParamMode,
/// What's the parameter's type? /// What's the parameter's type?
type_: Type, pub type_: Type,
/// The parameters span from any annotations to its type. /// The parameters span from any annotations to its type.
pub span: Span, pub span: Span,
} }
@ -65,10 +65,6 @@ impl FunctionInputVariable {
pub fn mode(&self) -> ParamMode { pub fn mode(&self) -> ParamMode {
self.mode self.mode
} }
pub fn type_(&self) -> Type {
self.type_.clone()
}
} }
impl FunctionInputVariable { impl FunctionInputVariable {

View File

@ -42,11 +42,8 @@ impl TryFrom<(Type, Expression)> for InputValue {
Self::Boolean(bool_value) Self::Boolean(bool_value)
} }
(Type::Char, ValueExpression::Char(value)) => Self::Char(value), (Type::Char, ValueExpression::Char(value)) => Self::Char(value),
(Type::Field, ValueExpression::Field(value, _) | ValueExpression::Implicit(value, _)) => { (Type::Field, ValueExpression::Field(value, _)) => Self::Field(value),
Self::Field(value)
}
(Type::Group, ValueExpression::Group(value)) => Self::Group(*value), (Type::Group, ValueExpression::Group(value)) => Self::Group(*value),
(Type::IntegerType(type_), ValueExpression::Implicit(value, _)) => Self::Integer(type_, value),
(Type::IntegerType(expected), ValueExpression::Integer(actual, value, span)) => { (Type::IntegerType(expected), ValueExpression::Integer(actual, value, span)) => {
if expected == actual { if expected == actual {
Self::Integer(expected, value) Self::Integer(expected, value)

View File

@ -275,7 +275,7 @@ impl<R: ReconstructingReducer> ReconstructingDirector<R> {
variable: &FunctionInputVariable, variable: &FunctionInputVariable,
) -> Result<FunctionInputVariable> { ) -> Result<FunctionInputVariable> {
let identifier = self.reduce_identifier(&variable.identifier)?; let identifier = self.reduce_identifier(&variable.identifier)?;
let type_ = self.reduce_type(&variable.type_(), &variable.span)?; let type_ = self.reduce_type(&variable.type_, &variable.span)?;
self.reducer.reduce_function_input_variable(variable, identifier, type_) self.reducer.reduce_function_input_variable(variable, identifier, type_)
} }

View File

@ -76,10 +76,7 @@ impl<'a, V: ExpressionVisitor<'a>> VisitorDirector<'a, V> {
pub fn visit_call(&mut self, input: &'a CallExpression) { pub fn visit_call(&mut self, input: &'a CallExpression) {
if let VisitResult::VisitChildren = self.visitor.visit_call(input) { if let VisitResult::VisitChildren = self.visitor.visit_call(input) {
self.visit_expression(&input.function); input.arguments.iter().for_each(|expr| self.visit_expression(expr));
for expr in input.arguments.iter() {
self.visit_expression(expr);
}
} }
} }
} }
@ -138,8 +135,11 @@ impl<'a, V: ExpressionVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V>
pub fn visit_console(&mut self, input: &'a ConsoleStatement) { pub fn visit_console(&mut self, input: &'a ConsoleStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_console(input) { if let VisitResult::VisitChildren = self.visitor.visit_console(input) {
if let ConsoleFunction::Assert(expr) = &input.function { match &input.function {
self.visit_expression(expr); ConsoleFunction::Assert(expr) => self.visit_expression(expr),
ConsoleFunction::Error(fmt) | ConsoleFunction::Log(fmt) => {
fmt.parameters.iter().for_each(|expr| self.visit_expression(expr));
}
} }
} }
} }
@ -152,9 +152,7 @@ impl<'a, V: ExpressionVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V>
pub fn visit_block(&mut self, input: &'a Block) { pub fn visit_block(&mut self, input: &'a Block) {
if let VisitResult::VisitChildren = self.visitor.visit_block(input) { if let VisitResult::VisitChildren = self.visitor.visit_block(input) {
for stmt in input.statements.iter() { input.statements.iter().for_each(|stmt| self.visit_statement(stmt));
self.visit_statement(stmt);
}
} }
} }
} }
@ -162,9 +160,10 @@ impl<'a, V: ExpressionVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V>
impl<'a, V: ExpressionVisitor<'a> + ProgramVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V> { impl<'a, V: ExpressionVisitor<'a> + ProgramVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V> {
pub fn visit_program(&mut self, input: &'a Program) { pub fn visit_program(&mut self, input: &'a Program) {
if let VisitResult::VisitChildren = self.visitor.visit_program(input) { if let VisitResult::VisitChildren = self.visitor.visit_program(input) {
for function in input.functions.values() { input
self.visit_function(function); .functions
} .values()
.for_each(|function| self.visit_function(function));
} }
} }

View File

@ -15,7 +15,6 @@
// 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::{Expression, Node}; use crate::{Expression, Node};
use leo_span::Span; use leo_span::Span;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -130,6 +130,8 @@ impl<'a> Compiler<'a> {
fn compiler_stages(&self) -> Result<SymbolTable<'_>> { fn compiler_stages(&self) -> Result<SymbolTable<'_>> {
let symbol_table = CreateSymbolTable::do_pass((&self.ast, self.handler))?; let symbol_table = CreateSymbolTable::do_pass((&self.ast, self.handler))?;
TypeChecker::do_pass((&self.ast, &mut symbol_table.clone(), self.handler))?;
Ok(symbol_table) Ok(symbol_table)
} }

View File

@ -86,13 +86,13 @@ impl Namespace for CompileNamespace {
#[derive(Deserialize, PartialEq, Serialize)] #[derive(Deserialize, PartialEq, Serialize)]
struct OutputItem { struct OutputItem {
pub initial_input_ast: String, pub initial_input_ast: String,
pub symbol_table: String,
} }
#[derive(Deserialize, PartialEq, Serialize)] #[derive(Deserialize, PartialEq, Serialize)]
struct CompileOutput { struct CompileOutput {
pub output: Vec<OutputItem>, pub output: Vec<OutputItem>,
pub initial_ast: String, pub initial_ast: String,
pub symbol_table: String,
} }
/// Get the path of the `input_file` given in `input` into `list`. /// Get the path of the `input_file` given in `input` into `list`.
@ -102,6 +102,12 @@ fn get_input_file_paths(list: &mut Vec<PathBuf>, test: &Test, input: &Value) {
let mut input_file = input_file; let mut input_file = input_file;
input_file.push(input.as_str().expect("input_file was not a string or array")); input_file.push(input.as_str().expect("input_file was not a string or array"));
list.push(input_file.clone()); list.push(input_file.clone());
} else if let Some(seq) = input.as_sequence() {
for name in seq {
let mut input_file = input_file.clone();
input_file.push(name.as_str().expect("input_file was not a string"));
list.push(input_file.clone());
}
} }
} }
@ -116,11 +122,7 @@ fn collect_all_inputs(test: &Test) -> Result<Vec<PathBuf>, String> {
Ok(list) Ok(list)
} }
fn compile_and_process<'a>( fn compile_and_process<'a>(parsed: &'a mut Compiler<'a>) -> Result<SymbolTable<'a>, LeoError> {
parsed: &'a mut Compiler<'a>,
input_file_path: PathBuf,
) -> Result<SymbolTable<'a>, LeoError> {
parsed.parse_input(input_file_path)?;
parsed.compiler_stages() parsed.compiler_stages()
} }
@ -176,24 +178,29 @@ fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Va
cwd.join(&val.as_str().unwrap()) cwd.join(&val.as_str().unwrap())
}); });
let parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd))?; let mut parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd))?;
// (name, content) // (name, content)
let inputs = buffer_if_err(err_buf, collect_all_inputs(&test))?; let inputs = buffer_if_err(err_buf, collect_all_inputs(&test))?;
let mut output_items = Vec::with_capacity(inputs.len()); let mut output_items = Vec::with_capacity(inputs.len());
for input in inputs { if inputs.is_empty() {
let mut parsed = parsed.clone();
let symbol_table = handler.extend_if_error(compile_and_process(&mut parsed, input))?;
let initial_input_ast = hash_file("/tmp/output/inital_input_ast.json");
output_items.push(OutputItem { output_items.push(OutputItem {
initial_input_ast, initial_input_ast: "no input".to_string(),
symbol_table: hash_content(&symbol_table.to_string()),
}); });
} else {
for input in inputs {
let mut parsed = parsed.clone();
handler.extend_if_error(parsed.parse_input(input))?;
let initial_input_ast = hash_file("/tmp/output/inital_input_ast.json");
output_items.push(OutputItem { initial_input_ast });
}
} }
let symbol_table = handler.extend_if_error(compile_and_process(&mut parsed))?;
let initial_ast = hash_file("/tmp/output/initial_ast.json"); let initial_ast = hash_file("/tmp/output/initial_ast.json");
if fs::read_dir("/tmp/output").is_ok() { if fs::read_dir("/tmp/output").is_ok() {
@ -203,6 +210,7 @@ fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Va
let final_output = CompileOutput { let final_output = CompileOutput {
output: output_items, output: output_items,
initial_ast, initial_ast,
symbol_table: hash_content(&symbol_table.to_string()),
}; };
Ok(serde_yaml::to_value(&final_output).expect("serialization failed")) Ok(serde_yaml::to_value(&final_output).expect("serialization failed"))
} }

View File

@ -32,6 +32,7 @@ pub(crate) const TYPE_TOKENS: &[Token] = &[
Token::Group, Token::Group,
Token::Address, Token::Address,
Token::Bool, Token::Bool,
Token::Char,
]; ];
impl ParserContext<'_> { impl ParserContext<'_> {

View File

@ -76,10 +76,6 @@ fn with_handler<T>(
Ok(parsed) Ok(parsed)
} }
fn implicit_value_expr() -> Expression {
Expression::Value(ValueExpression::Implicit("".into(), Span::default()))
}
fn tokenize(test: Test) -> Result<Vec<SpannedToken>, String> { fn tokenize(test: Test) -> Result<Vec<SpannedToken>, String> {
tokenizer::tokenize("test", &test.content).map_err(|x| x.to_string()) tokenizer::tokenize("test", &test.content).map_err(|x| x.to_string())
} }
@ -105,7 +101,7 @@ impl Namespace for ParseExpressionNamespace {
create_session_if_not_set_then(|_| { create_session_if_not_set_then(|_| {
let tokenizer = tokenize(test)?; let tokenizer = tokenize(test)?;
if all_are_comments(&tokenizer) { if all_are_comments(&tokenizer) {
return Ok(yaml_or_fail(implicit_value_expr())); return Ok(yaml_or_fail(""));
} }
with_handler(tokenizer, |p| p.parse_expression()).map(yaml_or_fail) with_handler(tokenizer, |p| p.parse_expression()).map(yaml_or_fail)
}) })
@ -124,7 +120,7 @@ impl Namespace for ParseStatementNamespace {
let tokenizer = tokenize(test)?; let tokenizer = tokenize(test)?;
if all_are_comments(&tokenizer) { if all_are_comments(&tokenizer) {
return Ok(yaml_or_fail(Statement::Expression(ExpressionStatement { return Ok(yaml_or_fail(Statement::Expression(ExpressionStatement {
expression: implicit_value_expr(), expression: Expression::Value(ValueExpression::String(Vec::new(), Default::default())),
span: Span::default(), span: Span::default(),
}))); })));
} }

View File

@ -16,7 +16,7 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
// disable // Temporarily disable canonicalization.
/* pub mod canonicalization; /* pub mod canonicalization;
pub use canonicalization::*; pub use canonicalization::*;
*/ */
@ -31,3 +31,6 @@ pub use self::pass::*;
pub mod symbol_table; pub mod symbol_table;
pub use symbol_table::*; pub use symbol_table::*;
pub mod type_checker;
pub use type_checker::*;

View File

@ -20,6 +20,9 @@ pub use create::*;
pub mod table; pub mod table;
pub use table::*; pub use table::*;
pub mod variable_scope;
pub use variable_scope::*;
pub mod variable_symbol; pub mod variable_symbol;
pub use variable_symbol::*; pub use variable_symbol::*;

View File

@ -22,7 +22,7 @@ use leo_span::Symbol;
use indexmap::IndexMap; use indexmap::IndexMap;
use crate::VariableSymbol; use crate::{VariableScope, VariableSymbol};
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SymbolTable<'a> { pub struct SymbolTable<'a> {
@ -31,7 +31,7 @@ pub struct SymbolTable<'a> {
functions: IndexMap<Symbol, &'a Function>, functions: IndexMap<Symbol, &'a Function>,
/// Variables represents functions variable definitions and input variables. /// Variables represents functions variable definitions and input variables.
/// This field is not populated till necessary. /// This field is not populated till necessary.
variables: VariableSymbol<'a>, pub(crate) variables: VariableScope<'a>,
} }
impl<'a> SymbolTable<'a> { impl<'a> SymbolTable<'a> {
@ -48,11 +48,39 @@ impl<'a> SymbolTable<'a> {
self.variables.clear(); self.variables.clear();
} }
pub fn insert_fn(&mut self, symbol: Symbol, function: &'a Function) -> Result<()> { pub fn insert_fn(&mut self, symbol: Symbol, insert: &'a Function) -> Result<()> {
self.check_shadowing(&symbol)?; self.check_shadowing(&symbol)?;
self.functions.insert(symbol, function); self.functions.insert(symbol, insert);
Ok(()) Ok(())
} }
pub fn insert_variable(&mut self, symbol: Symbol, insert: VariableSymbol<'a>) -> Result<()> {
self.check_shadowing(&symbol)?;
self.variables.variables.insert(symbol, insert);
Ok(())
}
pub fn lookup_fn(&self, symbol: &Symbol) -> Option<&&'a Function> {
self.functions.get(symbol)
}
pub fn lookup_variable(&self, symbol: &Symbol) -> Option<&VariableSymbol<'a>> {
self.variables.lookup_variable(symbol)
}
pub fn push_variable_scope(&mut self) {
let current_scope = self.variables.clone();
self.variables = VariableScope {
parent: Some(Box::new(current_scope)),
variables: Default::default(),
};
}
pub fn pop_variable_scope(&mut self) {
let parent = self.variables.parent.clone().unwrap();
self.variables = *parent;
}
} }
impl<'a> Display for SymbolTable<'a> { impl<'a> Display for SymbolTable<'a> {

View File

@ -0,0 +1,78 @@
// 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/>.
use std::fmt::Display;
use indexmap::IndexMap;
use leo_errors::{AstError, Result};
use leo_span::Symbol;
use crate::VariableSymbol;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VariableScope<'a> {
/// The parent scope of variables if it exists.
/// For example if we are in a if block inside a function.
/// The parent would be the functions variables and inputs.
/// This field is populated as necessary.
pub(crate) parent: Option<Box<VariableScope<'a>>>,
/// The variables defined in a scope.
/// This field is populated as necessary.
pub(crate) variables: IndexMap<Symbol, VariableSymbol<'a>>,
}
impl<'a> VariableScope<'a> {
pub fn check_shadowing(&self, symbol: &Symbol) -> Result<()> {
if let Some(var) = self.variables.get(symbol) {
Err(AstError::shadowed_variable(symbol, var.span).into())
} else if let Some(parent) = &self.parent {
parent.check_shadowing(symbol)
} else {
Ok(())
}
}
pub fn clear(&mut self) {
self.parent = None;
self.variables.clear();
}
pub fn lookup_variable(&self, symbol: &Symbol) -> Option<&VariableSymbol<'a>> {
if let Some(var) = self.variables.get(symbol) {
Some(var)
} else if let Some(parent) = &self.parent {
parent.lookup_variable(symbol)
} else {
None
}
}
}
impl<'a> Display for VariableScope<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "VariableScope")?;
self.parent
.as_ref()
.map(|parent| write!(f, "parent {parent}"))
.transpose()?;
for (sym, var) in self.variables.iter() {
write!(f, "{sym} {var}")?;
}
Ok(())
}
}

View File

@ -16,76 +16,38 @@
use std::fmt::Display; use std::fmt::Display;
use indexmap::IndexMap; use leo_ast::{ParamMode, Type};
use leo_ast::{DefinitionStatement, FunctionInput, FunctionInputVariable}; use leo_span::Span;
use leo_errors::{AstError, Result};
use leo_span::Symbol;
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct VariableSymbol<'a> { pub enum Declaration {
/// The parent scope of variables if it exists. Const,
/// For example if we are in a if block inside a function. Input(ParamMode),
/// The parent would be the functions variables and inputs. Mut,
/// This field is populated as necessary.
parent: Option<Box<VariableSymbol<'a>>>,
/// The input variables defined in a scope.
/// This field is populated as necessary.
inputs: IndexMap<Symbol, &'a FunctionInputVariable>,
/// The variables defined in a scope.
/// This field is populated as necessary.
variables: IndexMap<Symbol, &'a DefinitionStatement>,
} }
impl<'a> VariableSymbol<'a> { impl Display for Declaration {
pub fn new(parent: Option<Box<VariableSymbol<'a>>>, inputs: Vec<&'a FunctionInput>) -> Self { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Self { use Declaration::*;
parent,
inputs: inputs match self {
.iter() Const => write!(f, "const var"),
.map(|input| { Input(m) => write!(f, "{m} input"),
let inner = input.get_variable(); Mut => write!(f, "mut var"),
(inner.identifier.name, inner)
})
.collect(),
variables: IndexMap::new(),
} }
} }
}
pub fn check_shadowing(&self, symbol: &Symbol) -> Result<()> { #[derive(Clone, Debug, Eq, PartialEq)]
if let Some(input) = self.inputs.get(symbol) { pub struct VariableSymbol<'a> {
Err(AstError::shadowed_function_input(symbol, &input.span).into()) pub type_: &'a Type,
} else if let Some(var) = self.variables.get(symbol) { pub span: &'a Span,
Err(AstError::shadowed_variable(symbol, &var.span).into()) pub declaration: Declaration,
} else if let Some(parent) = &self.parent {
parent.check_shadowing(symbol)
} else {
Ok(())
}
}
pub fn clear(&mut self) {
self.parent = None;
self.inputs.clear();
self.variables.clear();
}
} }
impl<'a> Display for VariableSymbol<'a> { impl<'a> Display for VariableSymbol<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "VariableSymbol")?; write!(f, "{}: {}", self.declaration, self.type_)?;
self.parent
.as_ref()
.map(|parent| write!(f, "parent {parent}"))
.transpose()?;
for input in self.inputs.values() {
write!(f, "{input}")?;
}
for var in self.variables.values() {
write!(f, "{var}")?;
}
Ok(()) Ok(())
} }
} }

View File

@ -0,0 +1,317 @@
// 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/>.
use leo_ast::*;
use leo_errors::TypeCheckerError;
use leo_span::Span;
use crate::TypeChecker;
fn return_incorrect_type(t1: Option<Type>, t2: Option<Type>, expected: Option<Type>) -> Option<Type> {
match (t1, t2) {
(Some(t1), Some(t2)) if t1 == t2 => Some(t1),
(Some(t1), Some(t2)) => {
if let Some(expected) = expected {
if t1 != expected {
Some(t1)
} else {
Some(t2)
}
} else {
Some(t1)
}
}
(None, Some(_)) | (Some(_), None) | (None, None) => None,
}
}
impl<'a> TypeChecker<'a> {
pub(crate) fn compare_expr_type(&mut self, expr: &Expression, expected: Option<Type>, span: &Span) -> Option<Type> {
match expr {
Expression::Identifier(ident) => {
if let Some(var) = self.symbol_table.lookup_variable(&ident.name) {
Some(self.assert_type(var.type_.clone(), expected, span))
} else {
self.handler
.emit_err(TypeCheckerError::unknown_sym("variable", ident.name, span).into());
None
}
}
Expression::Value(value) => match value {
ValueExpression::Address(_, _) => Some(self.assert_type(Type::Address, expected, value.span())),
ValueExpression::Boolean(_, _) => Some(self.assert_type(Type::Boolean, expected, value.span())),
ValueExpression::Char(_) => Some(self.assert_type(Type::Char, expected, value.span())),
ValueExpression::Field(_, _) => Some(self.assert_type(Type::Field, expected, value.span())),
ValueExpression::Integer(type_, str_content, _) => {
match type_ {
IntegerType::I8 => {
let int = if self.negate {
self.negate = false;
format!("-{str_content}")
} else {
str_content.clone()
};
if int.parse::<i8>().is_err() {
self.handler
.emit_err(TypeCheckerError::invalid_int_value(int, "i8", value.span()).into());
}
}
IntegerType::I16 => {
let int = if self.negate {
self.negate = false;
format!("-{str_content}")
} else {
str_content.clone()
};
if int.parse::<i16>().is_err() {
self.handler
.emit_err(TypeCheckerError::invalid_int_value(int, "i16", value.span()).into());
}
}
IntegerType::I32 => {
let int = if self.negate {
self.negate = false;
format!("-{str_content}")
} else {
str_content.clone()
};
if int.parse::<i32>().is_err() {
self.handler
.emit_err(TypeCheckerError::invalid_int_value(int, "i32", value.span()).into());
}
}
IntegerType::I64 => {
let int = if self.negate {
self.negate = false;
format!("-{str_content}")
} else {
str_content.clone()
};
if int.parse::<i64>().is_err() {
self.handler
.emit_err(TypeCheckerError::invalid_int_value(int, "i64", value.span()).into());
}
}
IntegerType::I128 => {
let int = if self.negate {
self.negate = false;
format!("-{str_content}")
} else {
str_content.clone()
};
if int.parse::<i128>().is_err() {
self.handler
.emit_err(TypeCheckerError::invalid_int_value(int, "i128", value.span()).into());
}
}
IntegerType::U8 if str_content.parse::<u8>().is_err() => self
.handler
.emit_err(TypeCheckerError::invalid_int_value(str_content, "u8", value.span()).into()),
IntegerType::U16 if str_content.parse::<u16>().is_err() => self
.handler
.emit_err(TypeCheckerError::invalid_int_value(str_content, "u16", value.span()).into()),
IntegerType::U32 if str_content.parse::<u32>().is_err() => self
.handler
.emit_err(TypeCheckerError::invalid_int_value(str_content, "u32", value.span()).into()),
IntegerType::U64 if str_content.parse::<u64>().is_err() => self
.handler
.emit_err(TypeCheckerError::invalid_int_value(str_content, "u64", value.span()).into()),
IntegerType::U128 if str_content.parse::<u128>().is_err() => self
.handler
.emit_err(TypeCheckerError::invalid_int_value(str_content, "u128", value.span()).into()),
_ => {}
}
Some(self.assert_type(Type::IntegerType(*type_), expected, value.span()))
}
ValueExpression::Group(_) => Some(self.assert_type(Type::Group, expected, value.span())),
ValueExpression::String(_, _) => unreachable!("String types are not reachable"),
},
Expression::Binary(binary) => match binary.op {
BinaryOperation::And | BinaryOperation::Or => {
self.assert_type(Type::Boolean, expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
return_incorrect_type(t1, t2, expected)
}
BinaryOperation::Add | BinaryOperation::Sub => {
self.assert_arith_type(expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
return_incorrect_type(t1, t2, expected)
}
BinaryOperation::Mul => {
self.assert_arith_type(expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
match (t1.as_ref(), t2.as_ref()) {
(Some(Type::Group), Some(other)) | (Some(other), Some(Type::Group)) => {
self.assert_int_type(Some(other.clone()), binary.span());
Some(Type::Group)
}
_ => return_incorrect_type(t1, t2, expected),
}
}
BinaryOperation::Div => {
self.assert_field_or_int_type(expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, expected.clone(), binary.left.span());
let t2 = self.compare_expr_type(&binary.right, expected.clone(), binary.right.span());
return_incorrect_type(t1, t2, expected)
}
BinaryOperation::Pow => {
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
match (t1.as_ref(), t2.as_ref()) {
// Type A must be an int.
// Type B must be a unsigned int.
(Some(Type::IntegerType(_)), Some(Type::IntegerType(itype))) if !itype.is_signed() => {
self.assert_type(t1.clone().unwrap(), expected, binary.span());
}
// Type A was an int.
// But Type B was not a unsigned int.
(Some(Type::IntegerType(_)), Some(t)) => {
self.handler.emit_err(
TypeCheckerError::incorrect_pow_exponent_type("unsigned int", t, binary.right.span())
.into(),
);
}
// Type A must be a field.
// Type B must be an int.
(Some(Type::Field), Some(Type::IntegerType(_))) => {
self.assert_type(Type::Field, expected, binary.span());
}
// Type A was a field.
// But Type B was not an int.
(Some(Type::Field), Some(t)) => {
self.handler.emit_err(
TypeCheckerError::incorrect_pow_exponent_type("int", t, binary.right.span()).into(),
);
}
// The base is some type thats not an int or field.
(Some(t), _) => {
self.handler
.emit_err(TypeCheckerError::incorrect_pow_base_type(t, binary.left.span()).into());
}
_ => {}
}
t1
}
BinaryOperation::Eq | BinaryOperation::Ne => {
self.assert_type(Type::Boolean, expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
return_incorrect_type(t1, t2, expected)
}
BinaryOperation::Lt | BinaryOperation::Gt | BinaryOperation::Le | BinaryOperation::Ge => {
self.assert_type(Type::Boolean, expected.clone(), binary.span());
let t1 = self.compare_expr_type(&binary.left, None, binary.left.span());
self.assert_int_type(t1.clone(), binary.left.span());
let t2 = self.compare_expr_type(&binary.right, None, binary.right.span());
self.assert_int_type(t2.clone(), binary.right.span());
return_incorrect_type(t1, t2, expected)
}
},
Expression::Unary(unary) => match unary.op {
UnaryOperation::Not => {
self.assert_type(Type::Boolean, expected.clone(), unary.span());
self.compare_expr_type(&unary.inner, expected, unary.inner.span())
}
UnaryOperation::Negate => {
match expected.as_ref() {
Some(
Type::IntegerType(
IntegerType::I8
| IntegerType::I16
| IntegerType::I32
| IntegerType::I64
| IntegerType::I128,
)
| Type::Field
| Type::Group,
) => self.negate = !self.negate,
Some(t) => self
.handler
.emit_err(TypeCheckerError::type_is_not_negatable(t, unary.inner.span()).into()),
_ => {}
};
self.compare_expr_type(&unary.inner, expected, unary.inner.span())
}
},
Expression::Ternary(ternary) => {
self.compare_expr_type(&ternary.condition, Some(Type::Boolean), ternary.condition.span());
let t1 = self.compare_expr_type(&ternary.if_true, expected.clone(), ternary.if_true.span());
let t2 = self.compare_expr_type(&ternary.if_false, expected.clone(), ternary.if_false.span());
return_incorrect_type(t1, t2, expected)
}
Expression::Call(call) => match &*call.function {
Expression::Identifier(ident) => {
if let Some(func) = self.symbol_table.lookup_fn(&ident.name) {
let ret = self.assert_type(func.output.clone(), expected, ident.span());
if func.input.len() != call.arguments.len() {
self.handler.emit_err(
TypeCheckerError::incorrect_num_args_to_call(
func.input.len(),
call.arguments.len(),
call.span(),
)
.into(),
);
}
func.input
.iter()
.zip(call.arguments.iter())
.for_each(|(expected, argument)| {
self.compare_expr_type(
argument,
Some(expected.get_variable().type_.clone()),
argument.span(),
);
});
Some(ret)
} else {
self.handler
.emit_err(TypeCheckerError::unknown_sym("function", &ident.name, ident.span()).into());
None
}
}
expr => self.compare_expr_type(expr, expected, call.span()),
},
Expression::Err(_) => None,
}
}
}
impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {}

View File

@ -0,0 +1,42 @@
// 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/>.
use leo_ast::*;
use crate::{Declaration, TypeChecker, VariableSymbol};
impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
fn visit_function(&mut self, input: &'a Function) -> VisitResult {
self.symbol_table.clear_variables();
self.parent = Some(input.name());
input.input.iter().for_each(|i| {
let input_var = i.get_variable();
if let Err(err) = self.symbol_table.insert_variable(
input_var.identifier.name,
VariableSymbol {
type_: &input_var.type_,
span: input_var.identifier.span(),
declaration: Declaration::Input(input_var.mode()),
},
) {
self.handler.emit_err(err);
}
});
VisitResult::VisitChildren
}
}

View File

@ -0,0 +1,150 @@
// 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/>.
use leo_ast::*;
use leo_errors::TypeCheckerError;
use crate::{Declaration, TypeChecker, VariableSymbol};
impl<'a> StatementVisitor<'a> for TypeChecker<'a> {
fn visit_return(&mut self, input: &'a ReturnStatement) -> VisitResult {
// we can safely unwrap all self.parent instances because
// statements should always have some parent block
let parent = self.parent.unwrap();
// Would never be None.
let func_output_type = self.symbol_table.lookup_fn(&parent).map(|f| f.output.clone());
self.compare_expr_type(&input.expression, func_output_type, input.expression.span());
VisitResult::VisitChildren
}
fn visit_definition(&mut self, input: &'a DefinitionStatement) -> VisitResult {
let declaration = if input.declaration_type == Declare::Const {
Declaration::Const
} else {
Declaration::Mut
};
input.variable_names.iter().for_each(|v| {
if let Err(err) = self.symbol_table.insert_variable(
v.identifier.name,
VariableSymbol {
type_: &input.type_,
span: input.span(),
declaration: declaration.clone(),
},
) {
self.handler.emit_err(err);
}
self.compare_expr_type(&input.value, Some(input.type_.clone()), input.value.span());
});
VisitResult::VisitChildren
}
fn visit_assign(&mut self, input: &'a AssignStatement) -> VisitResult {
let var_name = &input.assignee.identifier.name;
let var_type = if let Some(var) = self.symbol_table.lookup_variable(var_name) {
match &var.declaration {
Declaration::Const => self
.handler
.emit_err(TypeCheckerError::cannont_assign_to_const_var(var_name, var.span).into()),
Declaration::Input(ParamMode::Constant) => self
.handler
.emit_err(TypeCheckerError::cannont_assign_to_const_input(var_name, var.span).into()),
_ => {}
}
Some(var.type_.clone())
} else {
self.handler.emit_err(
TypeCheckerError::unknown_sym("variable", &input.assignee.identifier.name, &input.assignee.span).into(),
);
None
};
if var_type.is_some() {
self.compare_expr_type(&input.value, var_type, input.value.span());
}
VisitResult::VisitChildren
}
fn visit_conditional(&mut self, input: &'a ConditionalStatement) -> VisitResult {
self.compare_expr_type(&input.condition, Some(Type::Boolean), input.condition.span());
VisitResult::VisitChildren
}
fn visit_iteration(&mut self, input: &'a IterationStatement) -> VisitResult {
if let Err(err) = self.symbol_table.insert_variable(
input.variable.name,
VariableSymbol {
type_: &input.type_,
span: input.span(),
declaration: Declaration::Const,
},
) {
self.handler.emit_err(err);
}
self.compare_expr_type(&input.start, Some(input.type_.clone()), input.start.span());
self.compare_expr_type(&input.stop, Some(input.type_.clone()), input.stop.span());
VisitResult::VisitChildren
}
fn visit_console(&mut self, input: &'a ConsoleStatement) -> VisitResult {
match &input.function {
ConsoleFunction::Assert(expr) => {
self.compare_expr_type(expr, Some(Type::Boolean), expr.span());
}
ConsoleFunction::Error(_) | ConsoleFunction::Log(_) => {
// TODO: undetermined
}
}
VisitResult::VisitChildren
}
fn visit_expression_statement(&mut self, input: &'a ExpressionStatement) -> VisitResult {
self.compare_expr_type(&input.expression, None, input.span());
VisitResult::SkipChildren
}
fn visit_block(&mut self, input: &'a Block) -> VisitResult {
self.symbol_table.push_variable_scope();
// have to redo the logic here so we have scoping
input.statements.iter().for_each(|stmt| {
match stmt {
Statement::Return(stmt) => self.visit_return(stmt),
Statement::Definition(stmt) => self.visit_definition(stmt),
Statement::Assign(stmt) => self.visit_assign(stmt),
Statement::Conditional(stmt) => self.visit_conditional(stmt),
Statement::Iteration(stmt) => self.visit_iteration(stmt),
Statement::Console(stmt) => self.visit_console(stmt),
Statement::Expression(stmt) => self.visit_expression_statement(stmt),
Statement::Block(stmt) => self.visit_block(stmt),
};
});
self.symbol_table.pop_variable_scope();
VisitResult::SkipChildren
}
}

View File

@ -0,0 +1,125 @@
// 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/>.
use leo_ast::{IntegerType, Type};
use leo_errors::{emitter::Handler, TypeCheckerError};
use leo_span::{Span, Symbol};
use crate::SymbolTable;
pub struct TypeChecker<'a> {
pub(crate) symbol_table: &'a mut SymbolTable<'a>,
pub(crate) handler: &'a Handler,
pub(crate) parent: Option<Symbol>,
pub(crate) negate: bool,
}
const ARITHMETIC_TYPES: &[Type] = &[
Type::Field,
Type::Group,
Type::IntegerType(IntegerType::I8),
Type::IntegerType(IntegerType::I16),
Type::IntegerType(IntegerType::I32),
Type::IntegerType(IntegerType::I64),
Type::IntegerType(IntegerType::I128),
Type::IntegerType(IntegerType::U8),
Type::IntegerType(IntegerType::U16),
Type::IntegerType(IntegerType::U32),
Type::IntegerType(IntegerType::U64),
Type::IntegerType(IntegerType::U128),
];
const FIELD_AND_INT_TYPES: &[Type] = &[
Type::Field,
Type::IntegerType(IntegerType::I8),
Type::IntegerType(IntegerType::I16),
Type::IntegerType(IntegerType::I32),
Type::IntegerType(IntegerType::I64),
Type::IntegerType(IntegerType::I128),
Type::IntegerType(IntegerType::U8),
Type::IntegerType(IntegerType::U16),
Type::IntegerType(IntegerType::U32),
Type::IntegerType(IntegerType::U64),
Type::IntegerType(IntegerType::U128),
];
const INT_TYPES: &[Type] = &[
Type::IntegerType(IntegerType::I8),
Type::IntegerType(IntegerType::I16),
Type::IntegerType(IntegerType::I32),
Type::IntegerType(IntegerType::I64),
Type::IntegerType(IntegerType::I128),
Type::IntegerType(IntegerType::U8),
Type::IntegerType(IntegerType::U16),
Type::IntegerType(IntegerType::U32),
Type::IntegerType(IntegerType::U64),
Type::IntegerType(IntegerType::U128),
];
impl<'a> TypeChecker<'a> {
pub fn new(symbol_table: &'a mut SymbolTable<'a>, handler: &'a Handler) -> Self {
Self {
symbol_table,
handler,
parent: None,
negate: false,
}
}
pub(crate) fn assert_type(&self, type_: Type, expected: Option<Type>, span: &Span) -> Type {
if let Some(expected) = expected {
if type_ != expected {
self.handler
.emit_err(TypeCheckerError::type_should_be(type_.clone(), expected, span).into());
}
}
type_
}
pub(crate) fn assert_one_of_types(&self, type_: Option<Type>, expected: &[Type], span: &Span) -> Option<Type> {
if let Some(type_) = type_.clone() {
for t in expected.iter() {
if &type_ == t {
return Some(type_);
}
}
self.handler.emit_err(
TypeCheckerError::expected_one_type_of(
expected.iter().map(|t| t.to_string() + ",").collect::<String>(),
type_,
span,
)
.into(),
);
}
type_
}
pub(crate) fn assert_arith_type(&self, type_: Option<Type>, span: &Span) -> Option<Type> {
self.assert_one_of_types(type_, ARITHMETIC_TYPES, span)
}
pub(crate) fn assert_field_or_int_type(&self, type_: Option<Type>, span: &Span) -> Option<Type> {
self.assert_one_of_types(type_, FIELD_AND_INT_TYPES, span)
}
pub(crate) fn assert_int_type(&self, type_: Option<Type>, span: &Span) -> Option<Type> {
self.assert_one_of_types(type_, INT_TYPES, span)
}
}

View File

@ -0,0 +1,47 @@
// 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/>.
pub mod check_expressions;
pub use check_expressions::*;
pub mod check_file;
pub use check_file::*;
pub mod check_statements;
pub use check_statements::*;
pub mod checker;
pub use checker::*;
use crate::{Pass, SymbolTable};
use leo_ast::{Ast, VisitorDirector};
use leo_errors::{emitter::Handler, Result};
impl<'a> Pass<'a> for TypeChecker<'a> {
type Input = (&'a Ast, &'a mut SymbolTable<'a>, &'a Handler);
type Output = Result<()>;
fn do_pass((ast, symbol_table, handler): Self::Input) -> Self::Output {
let mut visitor = VisitorDirector::new(TypeChecker::new(symbol_table, handler));
visitor.visit_program(ast.as_repr());
// todo @gluax: awkward cause last error double prints...
// but can't os exit or it causes tests to stop.
handler.last_err()?;
Ok(())
}
}

View File

@ -1,9 +0,0 @@
/*
namespace: Compile
expectation: Fail
input_file: input/dummy.in
*/
function main(a: u32, a: u32) {
console.log("{}", 1u8);
}

View File

@ -5,9 +5,6 @@ input_file: input/dummy.in
*/ */
function main() -> i8 { function main() -> i8 {
if true { return 2i8;
return 1i8; //ignored return 3i8;
}
return 2i8; //ignored
return 3i8; //returns
} }

View File

@ -1,7 +1,6 @@
/* /*
namespace: Compile namespace: Compile
expectation: Fail expectation: Fail
input_file: input/dummy.in
*/ */
const hi = 1u32; const hi = 1u32;

View File

@ -1,10 +0,0 @@
/*
namespace: Compile
expectation: Pass
input_file: ../input/dummy.in
*/
function main(y: bool) -> bool {
const a: i32 = 2147483647;
return y == true;
}

View File

@ -1,10 +0,0 @@
/*
namespace: Compile
expectation: Pass
input_file: ../input/dummy.in
*/
function main(y: bool) -> bool {
const a: i32 = -2147483648;
return y == true;
}

View File

@ -1,17 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- i32.in: |
[main]
a: i32 = 2;
b: i32 = 5;
c: i32 = 10;
[registers]
r0: bool = false;
*/
function main(a: i32, b: i32, c: i32) -> bool {
return a * b == c;
}

View File

@ -1,25 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- i32_ne.in: |
[main]
a: i32 = 2;
b: i32 = 5;
c: bool = true;
[registers]
r0: bool = false;
- i32_e.in: |
[main]
a: i32 = 5;
b: i32 = 5;
c: bool = false;
[registers]
r0: bool = false;
*/
function main(a: i32, b: i32, c: bool) -> bool{
return (a != b) == c;
}

View File

@ -1,23 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- i32.in: |
[main]
a: i32 = 5;
b: i32 = -5;
[registers]
r0: bool = false;
- i32.in: |
[main]
a: i32 = -5;
b: i32 = 5;
[registers]
r0: bool = false;
*/
function main(a: i32, b: i32) -> bool {
return -a == b;
}

View File

@ -1,17 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- dummy.in: |
[main]
y: bool = true;
[registers]
r0: bool = false;
*/
function main(y: bool) -> bool {
const a = 0i32;
return (-a == 0i32) == y;
}

View File

@ -1,3 +0,0 @@
function main(a: i32, b: i32, c: i32) -> bool {
return a ** b == c;
}

View File

@ -1,17 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- i32.in: |
[main]
a: i32 = 100;
b: i32 = 50;
c: i32 = 50;
[registers]
r0: bool = false;
*/
function main(a: i32, b: i32, c: i32) -> bool {
return a - b == c;
}

View File

@ -1,29 +0,0 @@
/*
namespace: Compile
expectation: Pass
inputs:
- i32.in: |
[main]
s: bool = true;
a: i32 = 10;
b: i32 = 5;
c: i32 = 10;
[registers]
r0: bool = false;
- i32_rev.in: |
[main]
s: bool = false;
a: i32 = 10;
b: i32 = 5;
c: i32 = 5;
[registers]
r0: bool = false;
*/
function main(s: bool, a: i32, b: i32, c: i32) -> bool {
let r = s ? a : b;
return r == c;
}

View File

@ -1,9 +1,12 @@
/* /*
namespace: Compile namespace: Compile
expectation: Fail expectation: Pass
input_file: ../input/dummy.in
*/ */
function main() { function main(y: bool) -> bool {
const a = -128i8; const a: = -128i8;
const b = -a; const b: = -a;
return (b == -a) == y;
} }

View File

@ -1,12 +0,0 @@
/*
namespace: Compile
expectation: Pass
input_file: input/dummy.in
*/
function main(a: bool) -> bool {
let x = 2u8;
let y = x;
let z = y / 2u8;
return (z == 1) == a;
}

View File

@ -1,29 +0,0 @@
/*
namespace: Compile
expectation: Pass
input_file: inputs/dummy.in
*/
function main(k: bool) -> bool {
let reverse: u32 = 0;
for i in 9..0 {
reverse += i;
}
let forward: u32 = 0;
for x in 0..10 {
forward += x;
}
let reverse_inclusive: u32 = 0;
for a in 10..=0 {
reverse_inclusive += a;
}
let forward_inclusive: u32 = 0;
for b in 0..=10 {
forward_inclusive += b;
}
return (reverse == forward) && (reverse_inclusive == forward_inclusive) && k;
}

View File

@ -1,16 +0,0 @@
/*
namespace: Compile
expectation: Pass
input_file: inputs/u32_3.in
*/
function main(x: u32) -> bool {
const COUNT: u32 = 2;
let y = x;
for i in 0..COUNT {
y -= 1;
}
return y == 1;
}

View File

@ -147,14 +147,6 @@ create_messages!(
help: None, help: None,
} }
/// For when a user shadows a function input.
@formatted
shadowed_function_input {
args: (fn_input: impl Display),
msg: format!("function input `{fn_input}` shadowed"),
help: None,
}
/// For when a user shadows a variable. /// For when a user shadows a variable.
@formatted @formatted
shadowed_variable { shadowed_variable {

View File

@ -20,7 +20,7 @@ use std::fmt::{Debug, Display};
create_messages!( create_messages!(
/// InputError enum that represents all the errors for the inputs part of `leo-ast` crate. /// InputError enum that represents all the errors for the inputs part of `leo-ast` crate.
InputError, InputError,
code_mask: 8000i32, code_mask: 1000i32,
code_prefix: "INP", code_prefix: "INP",
/// For when declared variable type mismatches actual type. /// For when declared variable type mismatches actual type.

View File

@ -41,6 +41,10 @@ pub use self::package::*;
pub mod parser; pub mod parser;
pub use self::parser::*; pub use self::parser::*;
/// Contains the Type Checker error definitions.
pub mod type_checker;
pub use self::type_checker::*;
/// The LeoError type that contains all sub error types. /// The LeoError type that contains all sub error types.
/// This allows a unified error type throughout the Leo crates. /// This allows a unified error type throughout the Leo crates.
#[derive(Clone, Debug, Error)] #[derive(Clone, Debug, Error)]
@ -63,6 +67,9 @@ pub enum LeoError {
/// Represents an Parser Error in a Leo Error. /// Represents an Parser Error in a Leo Error.
#[error(transparent)] #[error(transparent)]
ParserError(#[from] ParserError), ParserError(#[from] ParserError),
/// Represents an Type Checker Error in a Leo Error.
#[error(transparent)]
TypeCheckerError(#[from] TypeCheckerError),
} }
impl LeoError { impl LeoError {
@ -77,6 +84,7 @@ impl LeoError {
InputError(error) => error.error_code(), InputError(error) => error.error_code(),
ParserError(error) => error.error_code(), ParserError(error) => error.error_code(),
PackageError(error) => error.error_code(), PackageError(error) => error.error_code(),
TypeCheckerError(error) => error.error_code(),
} }
} }
@ -91,6 +99,7 @@ impl LeoError {
InputError(error) => error.exit_code(), InputError(error) => error.exit_code(),
ParserError(error) => error.exit_code(), ParserError(error) => error.exit_code(),
PackageError(error) => error.exit_code(), PackageError(error) => error.exit_code(),
TypeCheckerError(error) => error.exit_code(),
} }
} }
} }

View File

@ -291,9 +291,9 @@ create_messages!(
/// When the user tries to pass an implicit value. /// When the user tries to pass an implicit value.
@formatted @formatted
implicit_values_not_allowed { implicit_values_not_allowed {
args: (input: impl Display), args: (input: impl Display),
msg: format!("Could not parse the implicit value: {}.", input), msg: format!("Could not parse the implicit value: {}.", input),
help: None, help: None,
} }
/// When a escaped unicode char was given but no following closing symbol. /// When a escaped unicode char was given but no following closing symbol.

View File

@ -0,0 +1,19 @@
// 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/>.
/// This module contains the Input error definitions.
pub mod type_checker_error;
pub use self::type_checker_error::*;

View File

@ -0,0 +1,135 @@
// 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/>.
use crate::create_messages;
use std::fmt::{Debug, Display};
create_messages!(
/// InputError enum that represents all the errors for the inputs part of `leo-ast` crate.
TypeCheckerError,
code_mask: 2000i32,
code_prefix: "TYC",
/// For when the user tries to assign to a const input.
@formatted
cannont_assign_to_const_input {
args: (input: impl Display),
msg: format!(
"Cannot assign to const input `{input}`",
),
help: None,
}
/// For when the user tries to assign to a const input.
@formatted
cannont_assign_to_const_var {
args: (var: impl Display),
msg: format!(
"Cannot assign to const variable `{var}`",
),
help: None,
}
/// For when the user tries to assign to a const input.
@formatted
type_should_be {
args: (type_: impl Display, expected: impl Display),
msg: format!(
"Found type `{type_}` but type `{expected}` was expected",
),
help: None,
}
/// For when the user tries to return a unknown variable.
@formatted
unknown_sym {
args: (kind: impl Display, sym: impl Display),
msg: format!(
"Unknown {kind} `{sym}`",
),
help: None,
}
/// For when the user tries to expect a non integer type .
@formatted
type_should_be_integer {
args: (op: impl Debug, type_: impl Display),
msg: format!(
"Binary statement has numeric operation `{op:?}` but has expected type `{type_}`",
),
help: None,
}
/// For when the user tries to negate a non negatable type.
@formatted
type_is_not_negatable {
args: (type_: impl Display),
msg: format!(
"The type `{type_}` is not negatable",
),
help: None,
}
/// For when the user tries calls a function with the incorrect number of args.
@formatted
incorrect_num_args_to_call {
args: (expected: impl Display, received: impl Display),
msg: format!(
"Call expected `{expected}` args, but got `{received}`",
),
help: None,
}
/// For when one of the following types was expected.
@formatted
expected_one_type_of {
args: (expected: impl Display, received: impl Display),
msg: format!(
"Expected one type from `{expected}`, but got `{received}`",
),
help: None,
}
/// For when the base of a power is not a valid type.
@formatted
incorrect_pow_base_type {
args: (type_: impl Display),
msg: format!(
"The first operand must be an integer or field but got type `{type_}`",
),
help: None,
}
/// For when the exponent of a power is not a valid type.
@formatted
incorrect_pow_exponent_type {
args: (allowed: impl Display, type_: impl Display),
msg: format!(
"The second operand must be a {allowed} but got type `{type_}`",
),
help: None,
}
/// For when an integer is not in a valid range.
@formatted
invalid_int_value {
args: (value: impl Display, type_: impl Display),
msg: format!(
"The value {value} is not a valid `{type_}`",
),
help: None,
}
);

View File

@ -1,3 +0,0 @@
function main(a: group, b: group, c: group) {
console.assert(a + b == c);
}

View File

@ -1,3 +0,0 @@
function main(a: group, b: group) {
console.assert(a == b);
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (+, +)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (_, _)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (-, -)group;
}

View File

@ -1,3 +0,0 @@
function main(a: group, b: group, c: bool) {
console.assert((a == b) == c);
}

View File

@ -1,3 +0,0 @@
function main(a: group, b: group) {
console.assert(a == b);
}

View File

@ -1,3 +0,0 @@
[main]
a: group = (0, -)group;
b: group = (0, _)group;

View File

@ -1,3 +0,0 @@
function main(a: group, b: group) {
console.assert(-a == b);
}

View File

@ -1,3 +0,0 @@
function main() {
const g = (0,1) group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = 1group;
}

View File

@ -1,3 +0,0 @@
function main() {
const point = (7374112779530666882856915975292384652154477718021969292781165691637980424078, 3435195339177955418892975564890903138308061187980579490487898366607011481796)group;
}

View File

@ -1,3 +0,0 @@
function main(a: group) {
const b = a;
}

View File

@ -1,10 +0,0 @@
function main() {
const pos_element = 1group;
const neg_element = -1group;
const pair_x_pos = (1, _)group;
const pair_x_neg = (-1, _)group;
const pair_y_pos = (_, 1)group;
const pair_y_neg = (_, -1)group;
}

View File

@ -1,3 +0,0 @@
function main(a: group, b: group, c: group) {
console.assert(a - b == c);
}

View File

@ -1,5 +0,0 @@
function main(s: bool, a: group, b: group, c: group) {
const r = s ? a : b;
console.assert(r == c);
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (0, +)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (0, _)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (0, -)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (+, 1)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (_, 1)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = (-, 1)group;
}

View File

@ -1,3 +0,0 @@
function main() {
const element = 0group;
}

View File

@ -2,10 +2,10 @@
namespace: Compile namespace: Compile
expectation: Pass expectation: Pass
input_file: input_file:
- input/false_false.in - inputs/false_false.in
- input/false_true.in - inputs/false_true.in
- input/true_false.in - inputs/true_false.in
- input/true_true.in - inputs/true_true.in
*/ */
function main(a: bool, b: bool) -> bool { function main(a: bool, b: bool) -> bool {

View File

@ -2,10 +2,10 @@
namespace: Compile namespace: Compile
expectation: Pass expectation: Pass
input_file: input_file:
- input/false_false.in - inputs/false_false.in
- input/false_true.in - inputs/false_true.in
- input/true_false.in - inputs/true_false.in
- input/true_true.in - inputs/true_true.in
*/ */
function main(a: bool, b: bool) -> bool { function main(a: bool, b: bool) -> bool {

View File

@ -2,10 +2,10 @@
namespace: Compile namespace: Compile
expectation: Pass expectation: Pass
input_file: input_file:
- input/false_false.in - inputs/false_false.in
- input/false_true.in - inputs/false_true.in
- input/true_false.in - inputs/true_false.in
- input/true_true.in - inputs/true_true.in
*/ */
function main(a: bool, b: bool) -> bool { function main(a: bool, b: bool) -> bool {

View File

@ -2,10 +2,10 @@
namespace: Compile namespace: Compile
expectation: Pass expectation: Pass
input_file: input_file:
- input/false_false.in - inputs/false_false.in
- input/false_true.in - inputs/false_true.in
- input/true_false.in - inputs/true_false.in
- input/true_true.in - inputs/true_true.in
*/ */
function main(a: bool, b: bool) -> bool { function main(a: bool, b: bool) -> bool {

View File

@ -2,10 +2,10 @@
namespace: Compile namespace: Compile
expectation: Pass expectation: Pass
input_file: input_file:
- input/false_false.in - inputs/false_false.in
- input/false_true.in - inputs/false_true.in
- input/true_false.in - inputs/true_false.in
- input/true_true.in - inputs/true_true.in
*/ */
function main(a: bool, b: bool) -> bool { function main(a: bool, b: bool) -> bool {

Some files were not shown because too many files have changed in this diff Show More