contract self implementation
Description
close: #5905
Checklist
- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have requested support from the DevRel team
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] I have added (or requested a maintainer to add) the necessary
Breaking*orNew Featurelabels where relevant. - [x] I have done my best to ensure that my PR adheres to the Fuel Labs Code Review Standards.
- [x] I have requested a review from the relevant team or maintainers.
Thanks for the contribution! Before we can merge this, we need @hey-ewan to sign the Fuel Labs Contributor License Agreement.
CodSpeed Performance Report
Merging #7030 will not alter performance
Comparing hey-ewan:contract-self-impl (e03feea) with master (b59e1b6)
Summary
ā
22 untouched benchmarks
This needs some tests to validate that it even works.
@IGI-111 I added to the tests in forc-test as this directly tests the self impl feature in sway projects.
the feature generates a unique anonymous ABI with name __AnonymousAbi_N for each impl Contract block, where N is a unique counter.
simply put, the compiler takes an input like this:
impl Contract {
fn foo() -> u64 { 0 }
}
and generates this equivalent code:
abi __AnonymousAbi_1 {
fn foo() -> u64;
}
impl __AnonymousAbi_1 for Contract {
fn foo() -> u64 { 0 }
}
I've removed the test from forc-test and added a test contract contract_abi_auto_impl to test/src/e2e_vm_tests/test_programs/should_pass/test_abis, in it there's a unit test to ensure the anonymous abi is usable.
I also renamed __AnonymousAbi_{} to _AnonymousAbi_{} to prevent a compiler issue while trying to access the abi: Identifiers cannot begin with a double underscore, as that naming convention is reserved for compiler intrinsics.
@IGI-111, after doing some digging with the tests, I figured out that the std dep is needed for this, else the test fails. also there's a test contract should_fail/self_impl_contract that directly opposes this feature, hence I'll be removing that
Would be possible for the Abi to be named as the project is in Forc.toml? Probably changing the case to upper camel case.
"_Anonymous_Abi" sound strange. We use underscore only for intrinsics.
If we only have one "impl", we don't need a suffix I think.
Are we propagating all fn attributes like "read", "write" and others? I am seeing some "Attributes::default()" in the changes.
I think we should enable "validate_abi" in the "test.toml", like others tests do.
Would be possible for the Abi to be named as the project is in Forc.toml? Probably changing the case to upper camel case.
@xunilrj so I tried using the ManifestFile enum to get the name from the manifest, but that can't be imported due to the orphan rule. next I tried getting the current dir through env::current_dir() then parsing the toml file directly, but this falls short when tests aren't run from within any dir of the project being tested. how do you suggest I get the project name?
You cannot look at the current dir, as the compiler can point to another folder using --path.
I think the best solution is to create a new field at Context something like anonymous_contract_name, that can be filled at the start of the parsing with the best info we have.
And later we can pass this name to your get_project_name or use it directly inside handle_impl_contract.
What do you think? @hey-ewan
I think the best solution is to create a new field at
Contextsomething likeanonymous_contract_name, that can be filled at the start of the parsing with the best info we have.
@xunilrj what info do you think should be placed there initially?
This the patch that you can apply. We already have the package name, I am just passing it around a little bit more and using it directly. @hey-ewan
diff --git a/Cargo.lock b/Cargo.lock
index e3735a9b7..636976462 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8269,7 +8269,7 @@ dependencies = [
"swayfmt",
"sysinfo",
"thiserror 1.0.69",
- "toml 0.8.20",
+ "toml 0.8.22",
"tracing",
"uint",
"vec1",
diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs
index 5a3c72dc5..00e1cf3ee 100644
--- a/sway-core/src/lib.rs
+++ b/sway-core/src/lib.rs
@@ -98,9 +98,10 @@ pub fn parse(
engines: &Engines,
config: Option<&BuildConfig>,
experimental: ExperimentalFeatures,
+ package_name: &str
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
match config {
- None => parse_in_memory(handler, engines, src, experimental, DbgGeneration::None),
+ None => parse_in_memory(handler, engines, src, experimental, DbgGeneration::None, package_name),
// When a `BuildConfig` is given,
// the module source may declare `mod`s that must be parsed from other files.
Some(config) => parse_module_tree(
@@ -114,6 +115,7 @@ pub fn parse(
config.include_tests,
experimental,
config.lsp_mode.as_ref(),
+ package_name,
)
.map(
|ParsedModuleTree {
@@ -371,6 +373,7 @@ fn parse_in_memory(
src: Source,
experimental: ExperimentalFeatures,
dbg_generation: DbgGeneration,
+ package_name: &str,
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
let mut hasher = DefaultHasher::new();
src.text.hash(&mut hasher);
@@ -385,7 +388,7 @@ fn parse_in_memory(
let attributes_error_emitted = handler.append(attributes_handler);
let (kind, tree) = to_parsed_lang::convert_parse_tree(
- &mut to_parsed_lang::Context::new(BuildTarget::EVM, dbg_generation, experimental),
+ &mut to_parsed_lang::Context::new(BuildTarget::EVM, dbg_generation, experimental, package_name),
handler,
engines,
module.value.clone(),
@@ -438,6 +441,7 @@ fn parse_submodules(
include_tests: bool,
experimental: ExperimentalFeatures,
lsp_mode: Option<&LspConfig>,
+ package_name: &str,
) -> Submodules {
// Assume the happy path, so there'll be as many submodules as dependencies, but no more.
let mut submods = Vec::with_capacity(module.submodules().count());
@@ -471,6 +475,7 @@ fn parse_submodules(
include_tests,
experimental,
lsp_mode,
+ package_name,
) {
if !matches!(kind, parsed::TreeType::Library) {
let source_id = engines.se().get_source_id(submod_path.as_ref());
@@ -525,6 +530,7 @@ fn parse_module_tree(
include_tests: bool,
experimental: ExperimentalFeatures,
lsp_mode: Option<&LspConfig>,
+ package_name: &str,
) -> Result<ParsedModuleTree, ErrorEmitted> {
let query_engine = engines.qe();
@@ -546,6 +552,7 @@ fn parse_module_tree(
include_tests,
experimental,
lsp_mode,
+ package_name,
);
let (attributes_handler, attributes) = attr_decls_to_attributes(
@@ -557,7 +564,7 @@ fn parse_module_tree(
// Convert from the raw parsed module to the `ParseTree` ready for type-check.
let (kind, tree) = to_parsed_lang::convert_parse_tree(
- &mut to_parsed_lang::Context::new(build_target, dbg_generation, experimental),
+ &mut to_parsed_lang::Context::new(build_target, dbg_generation, experimental, package_name),
handler,
engines,
module.value.clone(),
@@ -1006,7 +1013,7 @@ pub fn compile_to_ast(
package_name,
"parse the program to a concrete syntax tree (CST)",
"parse_cst",
- parse(src, handler, engines, build_config, experimental),
+ parse(src, handler, engines, build_config, experimental, package_name),
build_config,
metrics
);
diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/mod.rs b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/mod.rs
index 3a98532f5..8544fa4e3 100644
--- a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/mod.rs
+++ b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/mod.rs
@@ -160,6 +160,7 @@ where
crate::BuildTarget::Fuel,
dbg_generation,
self.ctx.experimental,
+ "", // this is only used for self impl contracts
);
let handler = Handler::default();
@@ -236,6 +237,7 @@ where
BuildTarget::Fuel,
dbg_generation,
self.ctx.experimental,
+ "", // this is only used for self impl contracts
);
let handler = Handler::default();
diff --git a/sway-core/src/semantic_analysis/namespace/contract_helpers.rs b/sway-core/src/semantic_analysis/namespace/contract_helpers.rs
index 1547c7517..65e2717dc 100644
--- a/sway-core/src/semantic_analysis/namespace/contract_helpers.rs
+++ b/sway-core/src/semantic_analysis/namespace/contract_helpers.rs
@@ -72,7 +72,7 @@ fn bind_contract_id_in_root_module(
let attributes = Default::default();
// convert to const decl
let const_decl_id = to_parsed_lang::item_const_to_constant_declaration(
- &mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, dbg_generation, experimental),
+ &mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, dbg_generation, experimental, package.name().as_str()),
handler,
engines,
const_item,
diff --git a/sway-core/src/semantic_analysis/namespace/package.rs b/sway-core/src/semantic_analysis/namespace/package.rs
index f9c6df44f..0a0b35811 100644
--- a/sway-core/src/semantic_analysis/namespace/package.rs
+++ b/sway-core/src/semantic_analysis/namespace/package.rs
@@ -37,9 +37,9 @@ impl Package {
is_contract_package: bool,
) -> Self {
// The root module must be public
- let module = Module::new(package_name, Visibility::Public, span, &vec![]);
+ let root_module = Module::new(package_name, Visibility::Public, span, &vec![]);
Self {
- root_module: module,
+ root_module,
program_id,
is_contract_package,
external_packages: Default::default(),
diff --git a/sway-core/src/transform/to_parsed_lang/context.rs b/sway-core/src/transform/to_parsed_lang/context.rs
index 8b7b56ec2..ba243da94 100644
--- a/sway-core/src/transform/to_parsed_lang/context.rs
+++ b/sway-core/src/transform/to_parsed_lang/context.rs
@@ -36,6 +36,9 @@ pub struct Context {
/// Keeps track of the implementing type as we convert the tree.
pub(crate) implementing_type: Option<Declaration>,
+
+ /// Used to name anonymous impl contracts
+ pub package_name: String,
}
impl Context {
@@ -44,6 +47,7 @@ impl Context {
build_target: BuildTarget,
dbg_generation: DbgGeneration,
experimental: ExperimentalFeatures,
+ package_name: &str,
) -> Self {
Self {
build_target,
@@ -56,6 +60,7 @@ impl Context {
for_unique_suffix: std::default::Default::default(),
program_type: std::default::Default::default(),
implementing_type: None,
+ package_name: package_name.to_string(),
}
}
diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
index 7e079718d..ddbbdad3b 100644
--- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
+++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
@@ -35,12 +35,9 @@ use sway_error::{convert_parse_tree_error::ConvertParseTreeError, error::Compile
use sway_features::ExperimentalFeatures;
use sway_types::{integer_bits::IntegerBits, BaseIdent};
use sway_types::{style::to_upper_camel_case, Ident, Span, Spanned};
-
use std::{
- collections::HashSet, convert::TryFrom, env, fs, iter, mem::MaybeUninit, path::Path,
- str::FromStr,
+ collections::HashSet, convert::TryFrom, iter, mem::MaybeUninit, str::FromStr,
};
-use sway_utils::{constants::MANIFEST_FILE_NAME, find_parent_manifest_dir};
pub fn convert_parse_tree(
context: &mut Context,
@@ -885,46 +882,6 @@ pub fn item_impl_to_declaration(
}
}
-fn get_project_name() -> String {
- // Function to extract project name from a manifest file
- fn extract_project_name_from_manifest(manifest_path: &Path) -> Option<String> {
- fs::read_to_string(manifest_path).ok().and_then(|content| {
- toml::from_str::<toml::Value>(&content)
- .ok()
- .and_then(|value| {
- value
- .as_table()
- .and_then(|table| table.get("project"))
- .and_then(|project| project.as_table())
- .and_then(|project_table| project_table.get("name"))
- .and_then(|name| name.as_str())
- .map(|name| to_upper_camel_case(name))
- })
- })
- }
-
- // Get the current directory
- let current_dir = match env::current_dir() {
- Ok(dir) => dir,
- Err(_) => return "Anonymous".to_string(),
- };
-
- // Find the manifest directory (similar to ManifestFile::from_dir)
- let manifest_dir = match find_parent_manifest_dir(¤t_dir) {
- Some(dir) => dir,
- None => return "Anonymous".to_string(),
- };
-
- // Construct path to manifest file
- let manifest_path = manifest_dir.join(MANIFEST_FILE_NAME);
-
- // Extract the project name from the manifest
- match extract_project_name_from_manifest(&manifest_path) {
- Some(name) => name,
- None => "Anonymous".to_string(),
- }
-}
-
fn handle_impl_contract(
context: &mut Context,
handler: &Handler,
@@ -940,11 +897,10 @@ fn handle_impl_contract(
match item_impl.trait_opt {
Some((_, _)) => return Ok(vec![]),
None => {
- let project_name = get_project_name();
-
// Generate ABI name using project name with "Abi" suffix
+ let contract_name = to_upper_camel_case(context.package_name.as_str());
let anon_abi_name =
- Ident::new_with_override(format!("{}Abi", project_name), span.clone());
+ Ident::new_with_override(format!("{}Abi", contract_name), span.clone());
// Convert the methods to ABI interface
let mut interface_surface = Vec::new();
diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/contract_abi_auto_impl/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/contract_abi_auto_impl/src/main.sw
index 390dc659e..db4c98083 100644
--- a/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/contract_abi_auto_impl/src/main.sw
+++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/contract_abi_auto_impl/src/main.sw
@@ -6,7 +6,6 @@ impl Contract {
#[test]
fn tests() {
- // let caller = abi(ContractAbiAutoImplAbi, CONTRACT_ID); // If tested from within project's directory
- let caller = abi(AnonymousAbi, CONTRACT_ID); // If tested from outside project's directory
+ let caller = abi(ContractAbiAutoImplAbi, CONTRACT_ID);
assert(caller.impl_method() == 42)
}
@hey-ewan What do you think about my changes? Do you want to incorporate them, or you want me to create another PR on top or yours?
iād love if you created another pr, thanks
To be continued on https://github.com/FuelLabs/sway/pull/7275.