sway icon indicating copy to clipboard operation
sway copied to clipboard

contract self implementation

Open notxorand opened this issue 9 months ago • 11 comments

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).
  • [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* or New Feature labels 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.

notxorand avatar Mar 23 '25 07:03 notxorand

Thanks for the contribution! Before we can merge this, we need @hey-ewan to sign the Fuel Labs Contributor License Agreement.

fuel-cla-bot[bot] avatar Mar 23 '25 07:03 fuel-cla-bot[bot]

CodSpeed Performance Report

Merging #7030 will not alter performance

Comparing hey-ewan:contract-self-impl (e03feea) with master (b59e1b6)

Summary

āœ… 22 untouched benchmarks

codspeed-hq[bot] avatar Mar 24 '25 14:03 codspeed-hq[bot]

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 }
}

notxorand avatar Mar 26 '25 06:03 notxorand

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.

notxorand avatar Mar 28 '25 08:03 notxorand

@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

notxorand avatar Mar 31 '25 10:03 notxorand

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.

xunilrj avatar Apr 08 '25 09:04 xunilrj

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.

xunilrj avatar Apr 08 '25 09:04 xunilrj

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?

notxorand avatar Apr 08 '25 14:04 notxorand

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

xunilrj avatar May 30 '25 14:05 xunilrj

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.

@xunilrj what info do you think should be placed there initially?

notxorand avatar Jun 07 '25 12:06 notxorand

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(&current_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)
 }

xunilrj avatar Jun 18 '25 17:06 xunilrj

@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?

xunilrj avatar Jul 01 '25 17:07 xunilrj

i’d love if you created another pr, thanks

notxorand avatar Jul 03 '25 11:07 notxorand

To be continued on https://github.com/FuelLabs/sway/pull/7275.

xunilrj avatar Jul 07 '25 12:07 xunilrj