diff --git a/compiler/ast/src/passes/reconstructor.rs b/compiler/ast/src/passes/reconstructor.rs index b72b8f0033..60515c5786 100644 --- a/compiler/ast/src/passes/reconstructor.rs +++ b/compiler/ast/src/passes/reconstructor.rs @@ -315,7 +315,7 @@ pub trait ProgramReconstructor: StatementReconstructor { imports: input .imports .into_iter() - .map(|(id, import)| (id, self.reconstruct_import(import))) + .map(|(id, import)| (id, (self.reconstruct_import(import.0), import.1))) .collect(), program_scopes: input .program_scopes diff --git a/compiler/ast/src/passes/visitor.rs b/compiler/ast/src/passes/visitor.rs index 7ea7530920..73de6e5c2a 100644 --- a/compiler/ast/src/passes/visitor.rs +++ b/compiler/ast/src/passes/visitor.rs @@ -193,7 +193,7 @@ pub trait StatementVisitor<'a>: ExpressionVisitor<'a> { /// A Visitor trait for the program represented by the AST. pub trait ProgramVisitor<'a>: StatementVisitor<'a> { fn visit_program(&mut self, input: &'a Program) { - input.imports.values().for_each(|import| self.visit_import(import)); + input.imports.values().for_each(|import| self.visit_import(&import.0)); input .program_scopes diff --git a/compiler/ast/src/program/mod.rs b/compiler/ast/src/program/mod.rs index c5f0d6aed7..6a8292056c 100644 --- a/compiler/ast/src/program/mod.rs +++ b/compiler/ast/src/program/mod.rs @@ -24,6 +24,8 @@ pub use program_scope::*; use crate::Identifier; +use leo_span::Span; + use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::fmt; @@ -32,7 +34,7 @@ use std::fmt; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct Program { /// A map from import names to import definitions. - pub imports: IndexMap, + pub imports: IndexMap, /// A map from program names to program scopes. pub program_scopes: IndexMap, } diff --git a/compiler/parser/src/parser/expression.rs b/compiler/parser/src/parser/expression.rs index 64e95e68e4..536a63357b 100644 --- a/compiler/parser/src/parser/expression.rs +++ b/compiler/parser/src/parser/expression.rs @@ -389,7 +389,7 @@ impl ParserContext<'_> { } else { // Eat a struct member access. expr = Expression::Access(AccessExpression::Member(MemberAccess { - span: expr.span(), + span: expr.span() + name.span(), inner: Box::new(expr), name, })) diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index 673ed2f554..7728336206 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -71,10 +71,11 @@ impl ParserContext<'_> { ) } + // TODO: remove import resolution from parser. /// Parses an import statement `import foo.leo;`. - pub(super) fn parse_import(&mut self) -> Result<(Identifier, Program)> { + pub(super) fn parse_import(&mut self) -> Result<(Identifier, (Program, Span))> { // Parse `import`. - let _start = self.expect(&Token::Import)?; + let start = self.expect(&Token::Import)?; // Parse `foo`. let import_name = self.expect_identifier()?; @@ -86,7 +87,7 @@ impl ParserContext<'_> { return Err(ParserError::leo_imports_only(self.token.span).into()); } - let _end = self.expect(&Token::Semicolon)?; + let end = self.expect(&Token::Semicolon)?; // Tokenize and parse import file. // Todo: move this to a different module. @@ -114,7 +115,7 @@ impl ParserContext<'_> { // Use the parser to construct the imported abstract syntax tree (ast). let program_ast = parse_ast(self.handler, &prg_sf.src, prg_sf.start_pos)?; - Ok((import_name, program_ast.into_repr())) + Ok((import_name, (program_ast.into_repr(), start + end))) } /// Parsers a program scope `program foo.aleo { ... }`. diff --git a/compiler/passes/src/code_generation/visit_program.rs b/compiler/passes/src/code_generation/visit_program.rs index b391a79530..2fce3e8c5f 100644 --- a/compiler/passes/src/code_generation/visit_program.rs +++ b/compiler/passes/src/code_generation/visit_program.rs @@ -34,7 +34,7 @@ impl<'a> CodeGenerator<'a> { &input .imports .iter() - .map(|(identifier, imported_program)| self.visit_import(identifier, imported_program)) + .map(|(identifier, (imported_program, _))| self.visit_import(identifier, imported_program)) .join("\n"), ); diff --git a/compiler/passes/src/static_single_assignment/rename_program.rs b/compiler/passes/src/static_single_assignment/rename_program.rs index 13c4ccdcdd..71bcbc09b3 100644 --- a/compiler/passes/src/static_single_assignment/rename_program.rs +++ b/compiler/passes/src/static_single_assignment/rename_program.rs @@ -152,7 +152,7 @@ impl ProgramConsumer for StaticSingleAssigner<'_> { imports: input .imports .into_iter() - .map(|(name, import)| (name, self.consume_program(import))) + .map(|(name, (import, span))| (name, (self.consume_program(import), span))) .collect(), program_scopes: input .program_scopes diff --git a/compiler/passes/src/symbol_table/function_symbol.rs b/compiler/passes/src/symbol_table/function_symbol.rs index 8165f662e1..a108c530b4 100644 --- a/compiler/passes/src/symbol_table/function_symbol.rs +++ b/compiler/passes/src/symbol_table/function_symbol.rs @@ -38,7 +38,7 @@ pub struct FunctionSymbol { /// Is this function a transition, inlined, or a regular function?. pub call_type: CallType, /// The `Span` associated with the function. - pub(crate) span: Span, + pub(crate) _span: Span, /// The inputs to the function. pub(crate) input: Vec, /// Metadata associated with the finalize block. @@ -51,7 +51,7 @@ impl SymbolTable { id, output_type: func.output_type.clone(), call_type: func.call_type, - span: func.span, + _span: func.span, input: func.input.clone(), finalize: func.finalize.as_ref().map(|finalize| FinalizeData { input: finalize.input.clone(), diff --git a/compiler/passes/src/type_checking/check_expressions.rs b/compiler/passes/src/type_checking/check_expressions.rs index 867ad3352d..aa03326e81 100644 --- a/compiler/passes/src/type_checking/check_expressions.rs +++ b/compiler/passes/src/type_checking/check_expressions.rs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . +use crate::TypeChecker; + use leo_ast::*; use leo_errors::emitter::Handler; use leo_errors::TypeCheckerError; use leo_span::{sym, Span}; -use std::str::FromStr; -use crate::TypeChecker; +use std::str::FromStr; fn return_incorrect_type(t1: Option, t2: Option, expected: &Option) -> Option { match (t1, t2) { @@ -143,7 +144,14 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> { // Check that `access.name` is a member of the struct. match struct_.members.iter().find(|member| member.name() == access.name.name) { // Case where `access.name` is a member of the struct. - Some(Member { type_, .. }) => return Some(type_.clone()), + Some(Member { type_, .. }) => { + // Check that the type of `access.name` is the same as `expected`. + return Some(self.assert_and_return_type( + type_.clone(), + expected, + access.span(), + )); + } // Case where `access.name` is not a member of the struct. None => { self.emit_err(TypeCheckerError::invalid_struct_variable( @@ -454,7 +462,7 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> { } } - let ret = self.assert_and_return_type(func.output_type, expected, func.span); + let ret = self.assert_and_return_type(func.output_type, expected, input.span()); // Check number of function arguments. if func.input.len() != input.arguments.len() { @@ -537,11 +545,11 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> { Default::default() } - fn visit_identifier(&mut self, var: &'a Identifier, expected: &Self::AdditionalInput) -> Self::Output { - if let Some(var) = self.symbol_table.borrow().lookup_variable(var.name) { - Some(self.assert_and_return_type(var.type_.clone(), expected, var.span)) + fn visit_identifier(&mut self, input: &'a Identifier, expected: &Self::AdditionalInput) -> Self::Output { + if let Some(var) = self.symbol_table.borrow().lookup_variable(input.name) { + Some(self.assert_and_return_type(var.type_.clone(), expected, input.span())) } else { - self.emit_err(TypeCheckerError::unknown_sym("variable", var.name, var.span())); + self.emit_err(TypeCheckerError::unknown_sym("variable", input.name, input.span())); None } } diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 1f77487974..fb6a718a6b 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -287,4 +287,32 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { // Unset `is_transition_function` flag. self.is_transition_function = false; } + + fn visit_program(&mut self, input: &'a Program) { + match self.is_imported { + // If the program is imported, then it is not allowed to import any other programs. + true => { + input.imports.values().for_each(|(_, span)| { + self.emit_err(TypeCheckerError::imported_program_cannot_import_program(*span)) + }); + } + // Otherwise, typecheck the imported programs. + false => { + // Set `self.is_imported`. + let previous_is_imported = core::mem::replace(&mut self.is_imported, true); + + // Typecheck the imported programs. + input.imports.values().for_each(|import| self.visit_import(&import.0)); + + // Set `self.is_imported` to its previous state. + self.is_imported = previous_is_imported; + } + } + + // Typecheck the program scopes. + input + .program_scopes + .values() + .for_each(|scope| self.visit_program_scope(scope)); + } } diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index 5e6c7364ca..2ebda3353f 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -39,6 +39,8 @@ pub struct TypeChecker<'a> { pub(crate) is_transition_function: bool, /// Whether or not we are currently traversing a finalize block. pub(crate) is_finalize: bool, + /// Whether or not we are currently traversing an imported program. + pub(crate) is_imported: bool, } const BOOLEAN_TYPE: Type = Type::Boolean; @@ -95,6 +97,7 @@ impl<'a> TypeChecker<'a> { has_return: false, has_finalize: false, is_finalize: false, + is_imported: false, } } diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index 96324bbe17..444ab038a5 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -437,4 +437,11 @@ create_messages!( msg: format!("Strings are not yet supported."), help: None, } + + @formatted + imported_program_cannot_import_program { + args: (), + msg: format!("An imported program cannot import another program."), + help: None, + } ); diff --git a/tests/compiler/function/function_call_tyc_fail.leo b/tests/compiler/function/function_call_tyc_fail.leo new file mode 100644 index 0000000000..1c2ee60356 --- /dev/null +++ b/tests/compiler/function/function_call_tyc_fail.leo @@ -0,0 +1,26 @@ +/* +namespace: Compile +expectation: Fail +*/ + +program test.aleo { + + function f1(a: u8) -> u8 { + let b: u8 = a + 1u8; + return b; + } + + function f3(u2: u8, u3: i16) -> u8 { + return 0u8; + } + + transition main(id_type: i8, s: u8) -> u8 { + let x: i8 = 1i8; + x = f1(1u8); + + let y: i8 = 1i8; + let z: i16 = 100i16; + y = f3(y, z); + return s; + } +} diff --git a/tests/compiler/structs/struct_access_fail.leo b/tests/compiler/structs/struct_access_fail.leo new file mode 100644 index 0000000000..acaf1769a5 --- /dev/null +++ b/tests/compiler/structs/struct_access_fail.leo @@ -0,0 +1,19 @@ +/* +namespace: Compile +expectation: Fail +*/ + + +program test.aleo { + struct s1 { + f1: u32, + f2: u32 + } + + transition main(id_type: i8, s: s1) -> i8 { + let x: i8 = s.f1; + x = x + 1i8; + return x; + } +} + diff --git a/tests/expectations/compiler/function/function_call_tyc_fail.out b/tests/expectations/compiler/function/function_call_tyc_fail.out new file mode 100644 index 0000000000..4bf8563a2e --- /dev/null +++ b/tests/expectations/compiler/function/function_call_tyc_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372003]: Expected type `i8` but type `u8` was found\n --> compiler-test:16:13\n |\n 16 | x = f1(1u8);\n | ^^^^^^^\nError [ETYC0372003]: Expected type `i8` but type `u8` was found\n --> compiler-test:20:13\n |\n 20 | y = f3(y, z);\n | ^^^^^^^^\nError [ETYC0372003]: Expected type `u8` but type `i8` was found\n --> compiler-test:20:16\n |\n 20 | y = f3(y, z);\n | ^\n" diff --git a/tests/expectations/compiler/function/unknown_parameter_type_fail.out b/tests/expectations/compiler/function/unknown_parameter_type_fail.out index 9a55ebce55..67e62d45b4 100644 --- a/tests/expectations/compiler/function/unknown_parameter_type_fail.out +++ b/tests/expectations/compiler/function/unknown_parameter_type_fail.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - "Error [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:4:28\n |\n 4 | transition main(a: u8, foo: Foo) -> u8 {\n | ^^^\nError [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:8:38\n |\n 8 | transition returns_foo(a: u8) -> Foo {\n | ^^^\nError [ETYC0372003]: Expected type `Foo` but type `u8` was found\n --> compiler-test:8:28\n |\n 8 | transition returns_foo(a: u8) -> Foo {\n | ^\nError [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:8:5\n |\n 8 | transition returns_foo(a: u8) -> Foo {\n 9 | return a;\n 10 | }}\n | ^\n" + - "Error [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:4:28\n |\n 4 | transition main(a: u8, foo: Foo) -> u8 {\n | ^^^\nError [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:8:38\n |\n 8 | transition returns_foo(a: u8) -> Foo {\n | ^^^\nError [ETYC0372003]: Expected type `Foo` but type `u8` was found\n --> compiler-test:9:16\n |\n 9 | return a;\n | ^\nError [ETYC0372017]: The type `Foo` is not found in the current scope.\n --> compiler-test:8:5\n |\n 8 | transition returns_foo(a: u8) -> Foo {\n 9 | return a;\n 10 | }}\n | ^\n" diff --git a/tests/expectations/compiler/records/init_expression_type_fail.out b/tests/expectations/compiler/records/init_expression_type_fail.out index 9e9de4a094..920a0e1bed 100644 --- a/tests/expectations/compiler/records/init_expression_type_fail.out +++ b/tests/expectations/compiler/records/init_expression_type_fail.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - "Error [ETYC0372003]: Expected type `address` but type `u64` was found\n --> compiler-test:13:32\n |\n 13 | function mint(r0: address, r1: u64) -> Token {\n | ^^\nError [ETYC0372003]: Expected type `u64` but type `address` was found\n --> compiler-test:13:19\n |\n 13 | function mint(r0: address, r1: u64) -> Token {\n | ^^\nError [ETYC0372047]: Cannot call another function from a standard function.\n --> compiler-test:23:24\n |\n 23 | let t: Token = mint(x, c);\n | ^^^^^^^^^^\n" + - "Error [ETYC0372003]: Expected type `address` but type `u64` was found\n --> compiler-test:15:20\n |\n 15 | owner: r1, // This variable should be type address.\n | ^^\nError [ETYC0372003]: Expected type `u64` but type `address` was found\n --> compiler-test:17:21\n |\n 17 | amount: r0, // This variable should be type u64.\n | ^^\nError [ETYC0372047]: Cannot call another function from a standard function.\n --> compiler-test:23:24\n |\n 23 | let t: Token = mint(x, c);\n | ^^^^^^^^^^\n" diff --git a/tests/expectations/compiler/statements/assign_ternary.out b/tests/expectations/compiler/statements/assign_ternary.out index dd6bfcaceb..581039df99 100644 --- a/tests/expectations/compiler/statements/assign_ternary.out +++ b/tests/expectations/compiler/statements/assign_ternary.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - "Error [ETYC0372003]: Expected type `boolean` but type `u32` was found\n --> compiler-test:4:19\n |\n 4 | function main(x: u32) {\n | ^\nError [EAST0372011]: variable `x` shadowed by\n --> compiler-test:5:9\n |\n 5 | let x: bool = true ? x: true;\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Error [ETYC0372003]: Expected type `boolean` but type `u32` was found\n --> compiler-test:5:30\n |\n 5 | let x: bool = true ? x: true;\n | ^\nError [EAST0372011]: variable `x` shadowed by\n --> compiler-test:5:9\n |\n 5 | let x: bool = true ? x: true;\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" diff --git a/tests/expectations/compiler/structs/inline_member_pass.out b/tests/expectations/compiler/structs/inline_member_pass.out index c03ee2ab09..058949f252 100644 --- a/tests/expectations/compiler/structs/inline_member_pass.out +++ b/tests/expectations/compiler/structs/inline_member_pass.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - "Error [ETYC0372029]: Standard functions cannot have modes associated with their inputs.\n --> compiler-test:8:25\n |\n 8 | function main(const x: u8, y: bool) -> bool {\n | ^\n |\n = Consider removing the mode or using the keyword `transition` instead of `function`.\nError [ETYC0372005]: Unknown variable `b`\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^\nError [ETYC0372004]: Could not determine the type of `b`\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^\nError [ETYC0372003]: Expected type `u8` but type `no type` was found\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^^^^^^^^\n" + - "Error [ETYC0372029]: Standard functions cannot have modes associated with their inputs.\n --> compiler-test:8:25\n |\n 8 | function main(const x: u8, y: bool) -> bool {\n | ^\n |\n = Consider removing the mode or using the keyword `transition` instead of `function`.\nError [ETYC0372005]: Unknown variable `b`\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^\nError [ETYC0372004]: Could not determine the type of `b`\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^\nError [ETYC0372003]: Expected type `u8` but type `no type` was found\n --> compiler-test:11:17\n |\n 11 | return (b.x == a.x) == y;\n | ^^^^^^^^^^\n" diff --git a/tests/expectations/compiler/structs/struct_access_fail.out b/tests/expectations/compiler/structs/struct_access_fail.out new file mode 100644 index 0000000000..91015b412a --- /dev/null +++ b/tests/expectations/compiler/structs/struct_access_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372003]: Expected type `i8` but type `u32` was found\n --> compiler-test:11:21\n |\n 11 | let x: i8 = s.f1;\n | ^^^^\n"