Additional validation for program scope and imported files

This commit is contained in:
Pranav Gaddamadugu 2022-10-05 14:59:06 -07:00
parent 8f2a6fdb27
commit e15e8f05d6
12 changed files with 91 additions and 12 deletions

View File

@ -344,6 +344,7 @@ pub trait ProgramReconstructor: StatementReconstructor {
.into_iter()
.map(|(i, f)| (i, self.reconstruct_function(f)))
.collect(),
span: input.span,
}
}

View File

@ -19,6 +19,7 @@
use crate::{Function, Identifier, Mapping, Struct};
use indexmap::IndexMap;
use leo_span::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
@ -35,6 +36,8 @@ pub struct ProgramScope {
pub mappings: IndexMap<Identifier, Mapping>,
/// A map from function names to function definitions.
pub functions: IndexMap<Identifier, Function>,
/// The span associated with the program scope.
pub span: Span,
}
impl fmt::Display for ProgramScope {

View File

@ -51,6 +51,8 @@ pub struct Compiler<'a> {
pub input_ast: Option<InputAst>,
/// Compiler options on some optional output files.
output_options: OutputOptions,
/// Whether or not we are compiling an imported program.
is_import: bool,
}
impl<'a> Compiler<'a> {
@ -62,6 +64,7 @@ impl<'a> Compiler<'a> {
main_file_path: PathBuf,
output_directory: PathBuf,
output_options: Option<OutputOptions>,
is_import: bool,
) -> Self {
Self {
handler,
@ -72,6 +75,7 @@ impl<'a> Compiler<'a> {
ast: Ast::new(Program::default()),
input_ast: None,
output_options: output_options.unwrap_or_default(),
is_import,
}
}
@ -97,7 +101,20 @@ impl<'a> Compiler<'a> {
// Use the parser to construct the abstract syntax tree (ast).
self.ast = leo_parser::parse_ast(self.handler, &prg_sf.src, prg_sf.start_pos)?;
// TODO: Check that the program name matches the file name.
// If the program is imported, then check that the name of its program scope matches the file name.
// Note that parsing enforces that there is exactly one program scope in a file.
// TODO: Clean up check.
let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
let program_scope_name =
with_session_globals(|s| program_scope.name.name.as_str(s, |string| string.to_string()));
if self.is_import && program_scope_name != self.program_name {
return Err(CompilerError::imported_program_name_does_not_match_filename(
program_scope_name,
self.program_name.clone(),
program_scope.name.span,
)
.into());
}
if self.output_options.initial_ast {
self.write_ast_to_json("initial_ast.json")?;

View File

@ -60,6 +60,7 @@ fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_> {
ssa_ast: true,
flattened_ast: true,
}),
false,
)
}

View File

@ -28,7 +28,9 @@ impl ParserContext<'_> {
let mut imports = IndexMap::new();
let mut program_scopes = IndexMap::new();
// TODO: Condense logic
// TODO: Remove restrictions on multiple program scopes
let mut parsed_program_scope = false;
while self.has_next() {
match &self.token.token {
Token::Import => {
@ -36,12 +38,25 @@ impl ParserContext<'_> {
imports.insert(id, import);
}
Token::Program => {
match parsed_program_scope {
// Only one program scope is allowed per file.
true => return Err(ParserError::only_one_program_scope_is_allowed(self.token.span).into()),
false => {
parsed_program_scope = true;
let program_scope = self.parse_program_scope()?;
program_scopes.insert((program_scope.name, program_scope.network), program_scope);
}
}
}
_ => return Err(Self::unexpected_item(&self.token).into()),
}
}
// Requires that at least one program scope is present.
if !parsed_program_scope {
return Err(ParserError::missing_program_scope(self.token.span).into());
}
Ok(Program {
imports,
program_scopes,
@ -109,18 +124,22 @@ impl ParserContext<'_> {
/// Parsers a program scope `program foo.aleo { ... }`.
fn parse_program_scope(&mut self) -> Result<ProgramScope> {
// Parse `program` keyword.
let _start = self.expect(&Token::Program)?;
let start = self.expect(&Token::Program)?;
// Parse the program name.
let name = self.expect_identifier()?;
// Parse the program network.
self.expect(&Token::Dot)?;
let network = self.expect_identifier()?;
// Check that the program network is valid.
if network.name != sym::aleo {
return Err(ParserError::invalid_network(network.span).into());
}
// Parse `{`.
let _open = self.expect(&Token::LeftCurly)?;
self.expect(&Token::LeftCurly)?;
// Parse the body of the program scope.
let mut functions = IndexMap::new();
@ -148,7 +167,7 @@ impl ParserContext<'_> {
}
// Parse `}`.
let _close = self.expect(&Token::RightCurly)?;
let end = self.expect(&Token::RightCurly)?;
Ok(ProgramScope {
name,
@ -156,6 +175,7 @@ impl ParserContext<'_> {
functions,
structs,
mappings,
span: start + end,
})
}

View File

@ -101,6 +101,7 @@ impl ProgramScopeConsumer for StaticSingleAssigner {
.into_iter()
.map(|(i, f)| (i, self.consume_function(f)))
.collect(),
span: input.span,
}
}
}

View File

@ -63,4 +63,11 @@ create_messages!(
msg: format!("Program name `{program_name}` should match file name `{file_name}`"),
help: None,
}
@formatted
imported_program_name_does_not_match_filename {
args: (program_scope_name: impl Display, file_name: impl Display),
msg: format!("The program scope `{program_scope_name}` does not match its filename `{file_name}`."),
help: None,
}
);

View File

@ -302,7 +302,7 @@ create_messages!(
@backtraced
source_directory_can_contain_only_one_file {
args: (),
msg: "The `src/` directory can contain only one file.".to_string(),
msg: "The `src/` directory can contain only one file and must be named `main.leo`.".to_string(),
help: None,
}
);

View File

@ -250,4 +250,25 @@ create_messages!(
msg: "The keyword `circuit` is deprecated.",
help: Some("Use `struct` instead.".to_string()),
}
@formatted
only_one_program_scope_is_allowed {
args: (),
msg: "Only one program scope is allowed in a Leo file.",
help: None,
}
@formatted
missing_program_scope {
args: (),
msg: "Missing a program scope in a Leo file.",
help: Some("Add a program scope of the form: `program <name>.aleo { ... }` to the Leo file.".to_string()),
}
@formatted
invalid_network {
args: (),
msg: "Invalid network identifier. The only supported identifier is `aleo`.",
help: None,
}
);

View File

@ -137,6 +137,7 @@ impl Command for Build {
&build_directory,
&handler,
self.compiler_options.clone(),
false,
)?);
}
@ -157,6 +158,7 @@ impl Command for Build {
&build_imports_directory,
&handler,
self.compiler_options.clone(),
true,
)?);
}
}
@ -197,6 +199,8 @@ impl Command for Build {
}
}
/// Compiles a Leo file in the `src/` directory.
#[allow(clippy::too_many_arguments)]
fn compile_leo_file(
file_path: PathBuf,
_package_path: &Path,
@ -205,6 +209,7 @@ fn compile_leo_file(
build: &Path,
handler: &Handler,
options: BuildOptions,
is_import: bool,
) -> Result<IndexMap<Symbol, Struct>> {
// Construct the Leo file name with extension `foo.leo`.
let file_name = file_path
@ -218,13 +223,14 @@ fn compile_leo_file(
.ok_or_else(PackageError::failed_to_get_file_name)?;
// Create a new instance of the Leo compiler.
let mut program = Compiler::new(
let mut compiler = Compiler::new(
program_name.to_string(),
program_id.network().to_string(),
handler,
file_path.clone(),
outputs.to_path_buf(),
Some(options.into()),
is_import,
);
// TODO: Temporarily removing checksum files. Need to redesign this scheme.
@ -258,7 +264,7 @@ fn compile_leo_file(
// if checksum_differs {
// Compile the Leo program into Aleo instructions.
let (symbol_table, instructions) = program.compile_and_generate_instructions()?;
let (symbol_table, instructions) = compiler.compile_and_generate_instructions()?;
// Create the path to the Aleo file.
let mut aleo_file_path = build.to_path_buf();

View File

@ -18,6 +18,7 @@ use crate::parse_file_paths;
use leo_errors::{PackageError, Result};
use crate::source::MAIN_FILENAME;
use std::{
borrow::Cow,
fs,
@ -59,7 +60,7 @@ impl SourceDirectory {
pub fn check_files(paths: &[PathBuf]) -> Result<()> {
match paths.len() {
0 => Err(PackageError::empty_source_directory().into()),
1 => Ok(()),
1 if paths[0].as_path().ends_with(MAIN_FILENAME) => Ok(()),
_ => Err(PackageError::source_directory_can_contain_only_one_file().into()),
}
}

View File

@ -80,6 +80,7 @@ fn new_compiler(handler: &Handler) -> Compiler<'_> {
PathBuf::from(String::new()),
PathBuf::from(String::new()),
None,
false,
)
}