Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow methods defined in a contract to be non-entry points #2687

Merged
merged 6 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions compiler/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
let compiled_program = compile_no_check(context, options, main)?;

if options.print_acir {
println!("Compiled ACIR for main (unoptimized):");

Check warning on line 164 in compiler/noirc_driver/src/lib.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (unoptimized)
println!("{}", compiled_program.circuit);
}

Expand Down Expand Up @@ -204,7 +204,7 @@
if options.print_acir {
for contract_function in &compiled_contract.functions {
println!(
"Compiled ACIR for {}::{} (unoptimized):",

Check warning on line 207 in compiler/noirc_driver/src/lib.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (unoptimized)
compiled_contract.name, contract_function.name
);
println!("{}", contract_function.bytecode);
Expand Down Expand Up @@ -232,16 +232,28 @@
) -> Result<CompiledContract, ErrorsAndWarnings> {
let mut functions = Vec::new();
let mut errors = Vec::new();
for function_id in &contract.functions {
let name = context.function_name(function_id).to_owned();
let function = match compile_no_check(context, options, *function_id) {
for contract_function in &contract.functions {
let function_id = contract_function.function_id;
let is_entry_point = contract_function.is_entry_point;

let name = context.function_name(&function_id).to_owned();

// We assume that functions have already been type checked.
// This is the exact same assumption that compile_no_check makes.
// If it is not an entry-point point, we can then just skip the
// compilation step. It will also not be added to the ABI.
if !is_entry_point {
continue;
}

let function = match compile_no_check(context, options, function_id) {
Ok(function) => function,
Err(new_error) => {
errors.push(new_error);
continue;
}
};
let func_meta = context.def_interner.function_meta(function_id);
let func_meta = context.def_interner.function_meta(&function_id);
let func_type = func_meta
.contract_function_type
.expect("Expected contract function to have a contract visibility");
Expand Down
28 changes: 25 additions & 3 deletions compiler/noirc_frontend/src/hir/def_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,23 @@ impl CrateDefMap {

/// Go through all modules in this crate, find all `contract ... { ... }` declarations,
/// and collect them all into a Vec.
pub fn get_all_contracts(&self) -> Vec<Contract> {
pub fn get_all_contracts(&self, interner: &NodeInterner) -> Vec<Contract> {
self.modules
.iter()
.filter_map(|(id, module)| {
if module.is_contract {
let functions =
let function_ids: Vec<FuncId> =
module.value_definitions().filter_map(|id| id.as_function()).collect();

let functions = function_ids
.into_iter()
.map(|id| {
Comment on lines +166 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't collect into function_ids then call into_iter immediately afterward, the intermediate Vec is not needed.

let is_entry_point =
!interner.function_attributes(&id).has_contract_library_method();
ContractFunctionMeta { function_id: id, is_entry_point }
})
.collect();

let name = self.get_module_path(id, module.parent);
Some(Contract { name, location: module.location, functions })
} else {
Expand Down Expand Up @@ -204,13 +214,25 @@ impl CrateDefMap {
}
}

/// Specifies a contract function and extra metadata that
/// one can use when processing a contract function.
///
/// One of these is whether the contract function is an entry point.
/// The caller should only type-check these functions and not attempt
/// to create a circuit for them.
pub struct ContractFunctionMeta {
pub function_id: FuncId,
/// Indicates whether the function is an entry point
pub is_entry_point: bool,
}

/// A 'contract' in Noir source code with the given name and functions.
/// This is not an AST node, it is just a convenient form to return for CrateDefMap::get_all_contracts.
pub struct Contract {
/// To keep `name` semi-unique, it is prefixed with the names of parent modules via CrateDefMap::get_module_path
pub name: String,
pub location: Location,
pub functions: Vec<FuncId>,
pub functions: Vec<ContractFunctionMeta>,
}

/// Given a FileId, fetch the File, from the FileManager and parse it's content
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl Context {
pub fn get_all_contracts(&self, crate_id: &CrateId) -> Vec<Contract> {
self.def_map(crate_id)
.expect("The local crate should be analyzed already")
.get_all_contracts()
.get_all_contracts(&self.def_interner)
}

fn module(&self, module_id: def_map::ModuleId) -> &def_map::ModuleData {
Expand Down
13 changes: 13 additions & 0 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,19 @@ mod tests {
&Token::Attribute(Attribute::Primary(PrimaryAttribute::Test(TestScope::None)))
);
}

#[test]
fn contract_library_method_attribute() {
let input = r#"#[contract_library_method]"#;
let mut lexer = Lexer::new(input);

let token = lexer.next().unwrap().unwrap();
assert_eq!(
token.token(),
&Token::Attribute(Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod))
);
}

#[test]
fn test_attribute_with_valid_scope() {
let input = r#"#[test(should_fail)]"#;
Expand Down
19 changes: 19 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@
match string.trim() {
"should_fail" => Some(TestScope::ShouldFailWith { reason: None }),
s if s.starts_with("should_fail_with") => {
let parts: Vec<&str> = s.splitn(2, '=').collect();

Check warning on line 335 in compiler/noirc_frontend/src/lexer/token.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (splitn)
if parts.len() == 2 {
let reason = parts[1].trim();
let reason = reason.trim_matches('"');
Expand Down Expand Up @@ -374,6 +374,16 @@
Self { primary: None, secondary: Vec::new() }
}

/// Returns true if one of the secondary attributes is `contract_library_method`
///
/// This is useful for finding out if we should compile a contract method
/// as an entry point or not.
pub fn has_contract_library_method(&self) -> bool {
self.secondary
.iter()
.any(|attribute| attribute == &SecondaryAttribute::ContractLibraryMethod)
}

/// Returns note if a deprecated secondary attribute is found
pub fn get_deprecated_note(&self) -> Option<Option<String>> {
self.secondary.iter().find_map(|attr| match attr {
Expand Down Expand Up @@ -454,6 +464,9 @@
}
// Secondary attributes
["deprecated"] => Attribute::Secondary(SecondaryAttribute::Deprecated(None)),
["contract_library_method"] => {
Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod)
}
["deprecated", name] => {
if !name.starts_with('"') && !name.ends_with('"') {
return Err(LexerErrorKind::MalformedFuncAttribute {
Expand Down Expand Up @@ -527,6 +540,10 @@
#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
pub enum SecondaryAttribute {
Deprecated(Option<String>),
// This is an attribute to specify that a function
// is a helper method for a contract and should not be seen as
// the entry point.
ContractLibraryMethod,
Custom(String),
}

Expand All @@ -538,6 +555,7 @@
write!(f, r#"#[deprecated("{note}")]"#)
}
SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"),
SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"),
}
}
}
Expand All @@ -559,6 +577,7 @@
SecondaryAttribute::Deprecated(Some(string)) => string,
SecondaryAttribute::Deprecated(None) => "",
SecondaryAttribute::Custom(string) => string,
SecondaryAttribute::ContractLibraryMethod => "",
}
}
}
Expand Down Expand Up @@ -623,7 +642,7 @@
Keyword::Field => write!(f, "Field"),
Keyword::Fn => write!(f, "fn"),
Keyword::For => write!(f, "for"),
Keyword::FormatString => write!(f, "fmtstr"),

Check warning on line 645 in compiler/noirc_frontend/src/lexer/token.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (fmtstr)
Keyword::Global => write!(f, "global"),
Keyword::If => write!(f, "if"),
Keyword::Impl => write!(f, "impl"),
Expand Down Expand Up @@ -666,7 +685,7 @@
"Field" => Keyword::Field,
"fn" => Keyword::Fn,
"for" => Keyword::For,
"fmtstr" => Keyword::FormatString,

Check warning on line 688 in compiler/noirc_frontend/src/lexer/token.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (fmtstr)
"global" => Keyword::Global,
"if" => Keyword::If,
"impl" => Keyword::Impl,
Expand Down
5 changes: 5 additions & 0 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
function::{FuncMeta, HirFunction},
stmt::HirStatement,
};
use crate::token::Attributes;
use crate::{
Generics, Shared, TypeAliasType, TypeBinding, TypeBindings, TypeVariable, TypeVariableId,
TypeVariableKind,
Expand Down Expand Up @@ -76,7 +77,7 @@
// Trait implementation map
// For each type that implements a given Trait ( corresponding TraitId), there should be an entry here
// The purpose for this hashmap is to detect duplication of trait implementations ( if any )
trait_implementaions: HashMap<(Type, TraitId), Ident>,

Check warning on line 80 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaions)

/// Map from ExprId (referring to a Function/Method call) to its corresponding TypeBindings,
/// filled out during type checking from instantiated variables. Used during monomorphization
Expand Down Expand Up @@ -304,7 +305,7 @@
structs: HashMap::new(),
type_aliases: Vec::new(),
traits: HashMap::new(),
trait_implementaions: HashMap::new(),

Check warning on line 308 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaions)
instantiation_bindings: HashMap::new(),
field_indices: HashMap::new(),
next_type_variable_id: 0,
Expand Down Expand Up @@ -547,6 +548,10 @@
self.definition_name(name_id)
}

pub fn function_attributes(&self, func_id: &FuncId) -> Attributes {
self.function_meta(func_id).attributes.clone()
}

/// Returns the interned statement corresponding to `stmt_id`
pub fn statement(&self, stmt_id: &StmtId) -> HirStatement {
let def =
Expand Down Expand Up @@ -712,16 +717,16 @@
}

pub fn get_previous_trait_implementation(&self, key: &(Type, TraitId)) -> Option<&Ident> {
self.trait_implementaions.get(key)

Check warning on line 720 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaions)
}

pub fn add_trait_implementaion(

Check warning on line 723 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaion)
&mut self,
key: &(Type, TraitId),
trait_definition_ident: &Ident,
methods: &UnresolvedFunctions,
) -> Vec<FuncId> {
self.trait_implementaions.insert(key.clone(), trait_definition_ident.clone());

Check warning on line 729 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaions)
methods
.functions
.iter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "no_entry_points"
type = "contract"
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contract Foo {
struct PlaceholderStruct{x : u32 }

#[contract_library_method]
fn has_mut(_context : &mut PlaceholderStruct) {}
}