Skip to content

Commit

Permalink
Supertraits for ABIs (FuelLabs#3992)
Browse files Browse the repository at this point in the history
## Description

Please see the issue FuelLabs#3798.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] 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](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

## Tasks

- [x] check the supertrait(s) for the ABI is implemented
- [x] fail with an error when a supertrait for ABI is not implemented
- [x] check that ABI implementation methods can use its supertrait's
methods (the one that are defined at the ABI _declaration_ site)
- [x] check that the supertrait's methods can be used in the actual
contract methods
- [x] check that methods in supertraits are not be available externally
(i.e. they're not actually contract methods)
- [x] implement that Ownable example
- [x] formatter tests
- [x] add new section in the Sway docs illustrating this language
feature

---------

Co-authored-by: Mohammad Fawaz <[email protected]>
  • Loading branch information
anton-trunov and mohammadfawaz authored Feb 16, 2023
1 parent 307feca commit 8ce3600
Show file tree
Hide file tree
Showing 72 changed files with 940 additions and 11 deletions.
12 changes: 12 additions & 0 deletions docs/book/src/advanced/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ impl Ord for u64 {

To require a supertrait, add a `:` after the trait name and then list the traits you would like to require and separate them with a `+`.

### ABI supertraits

ABIs can also have supertrait annotations:

```sway
{{#include ../../../../examples/abi_supertraits/src/main.sw}}
```

The implementation of `MyAbi` for `Contract` must also implement the `ABIsupertrait` trait. Methods in `ABIsupertrait` are not available externally, i.e. they're not actually contract methods, but they can be used in the actual contract methods, as shown in the example above.

ABI supertraits are intended to make contract implementations compositional, allowing combining orthogonal contract features using, for instance, libraries.

## Use Cases

### Custom Types (structs, enums)
Expand Down
3 changes: 3 additions & 0 deletions examples/abi_supertraits/Forc.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = 'abi_supertraits'
source = 'member'
6 changes: 6 additions & 0 deletions examples/abi_supertraits/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
implicit-std = false
license = "Apache-2.0"
name = "abi_supertraits"
24 changes: 24 additions & 0 deletions examples/abi_supertraits/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
contract;

trait ABIsupertrait {
fn foo();
}

abi MyAbi : ABIsupertrait {
fn bar();
} {
fn baz() {
Self::foo() // supertrait method usage
}
}

impl ABIsupertrait for Contract {
fn foo() {}
}

// The implementation of MyAbi for Contract must also implement ABIsupertrait
impl MyAbi for Contract {
fn bar() {
Self::foo() // supertrait method usage
}
}
1 change: 1 addition & 0 deletions sway-ast/src/item/item_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::priv_prelude::*;
pub struct ItemAbi {
pub abi_token: AbiToken,
pub name: Ident,
pub super_traits: Option<(ColonToken, Traits)>,
pub abi_items: Braces<Vec<(Annotated<FnSignature>, SemicolonToken)>>,
pub abi_defs_opt: Option<Braces<Vec<Annotated<ItemFn>>>>,
}
Expand Down
3 changes: 2 additions & 1 deletion sway-core/src/language/parsed/declaration/abi.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::transform;

use super::{FunctionDeclaration, TraitFn};
use super::{FunctionDeclaration, Supertrait, TraitFn};

use sway_types::{ident::Ident, span::Span};

Expand All @@ -12,6 +12,7 @@ pub struct AbiDeclaration {
pub name: Ident,
/// The methods a contract is required to implement in order opt in to this interface
pub interface_surface: Vec<TraitFn>,
pub supertraits: Vec<Supertrait>,
/// The methods provided to a contract "for free" upon opting in to this interface
pub methods: Vec<FunctionDeclaration>,
pub(crate) span: Span,
Expand Down
8 changes: 7 additions & 1 deletion sway-core/src/language/ty/declaration/abi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{decl_engine::DeclRef, engine_threading::*, transform, type_system::*};
use crate::{
decl_engine::DeclRef, engine_threading::*, language::parsed, transform, type_system::*,
};
use std::hash::{Hash, Hasher};

use sway_types::{Ident, Span, Spanned};

/// A [TyAbiDeclaration] contains the type-checked version of the parse tree's `AbiDeclaration`.
Expand All @@ -9,6 +12,7 @@ pub struct TyAbiDeclaration {
pub name: Ident,
/// The methods a contract is required to implement in order opt in to this interface
pub interface_surface: Vec<DeclRef>,
pub supertraits: Vec<parsed::Supertrait>,
pub methods: Vec<DeclRef>,
pub span: Span,
pub attributes: transform::AttributesMap,
Expand All @@ -29,6 +33,7 @@ impl HashWithEngines for TyAbiDeclaration {
name,
interface_surface,
methods,
supertraits,
// these fields are not hashed because they aren't relevant/a
// reliable source of obj v. obj distinction
attributes: _,
Expand All @@ -37,6 +42,7 @@ impl HashWithEngines for TyAbiDeclaration {
name.hash(state);
interface_surface.hash(state, engines);
methods.hash(state, engines);
supertraits.hash(state, engines);
}
}

Expand Down
18 changes: 13 additions & 5 deletions sway-core/src/language/ty/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ impl TyProgram {
_ => {}
},
// ABI entries are all functions declared in impl_traits on the contract type
// itself.
// itself, except for ABI supertraits, which do not expose their methods to
// the user
TyAstNodeContent::Declaration(TyDeclaration::ImplTrait { decl_id, .. }) => {
let TyImplTrait {
methods,
implementing_for,
span,
trait_decl_ref,
..
} = check!(
CompileResult::from(decl_engine.get_impl_trait(decl_id, &node.span)),
Expand All @@ -118,10 +120,16 @@ impl TyProgram {
errors
);
if matches!(ty_engine.get(implementing_for.type_id), TypeInfo::Contract) {
for method_ref in methods {
match decl_engine.get_function(&method_ref, &span) {
Ok(method) => abi_entries.push(method),
Err(err) => errors.push(err),
// add methods to the ABI only if they come from an ABI implementation
// and not a (super)trait implementation for Contract
if let Some(trait_decl_ref) = trait_decl_ref {
if decl_engine.get_abi(&trait_decl_ref, &span).is_ok() {
for method_ref in methods {
match decl_engine.get_function(&method_ref, &span) {
Ok(method) => abi_entries.push(method),
Err(err) => errors.push(err),
}
}
}
}
}
Expand Down
19 changes: 18 additions & 1 deletion sway-core/src/semantic_analysis/ast_node/declaration/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sway_types::Spanned;
use crate::{
error::*,
language::{parsed::*, ty},
semantic_analysis::{Mode, TypeCheckContext},
semantic_analysis::{declaration::insert_supertraits_into_namespace, Mode, TypeCheckContext},
CompileResult,
};

Expand All @@ -19,6 +19,7 @@ impl ty::TyAbiDeclaration {
let AbiDeclaration {
name,
interface_surface,
supertraits,
methods,
span,
attributes,
Expand All @@ -30,9 +31,21 @@ impl ty::TyAbiDeclaration {
// from itself. This is by design.

// A temporary namespace for checking within this scope.
let type_engine = ctx.type_engine;
let decl_engine = ctx.decl_engine;
let contract_type = type_engine.insert(decl_engine, crate::TypeInfo::Contract);
let mut abi_namespace = ctx.namespace.clone();
let mut ctx = ctx.scoped(&mut abi_namespace).with_mode(Mode::ImplAbiFn);

// Recursively make the interface surfaces and methods of the
// supertraits available to this abi.
check!(
insert_supertraits_into_namespace(ctx.by_ref(), contract_type, &supertraits),
return err(warnings, errors),
warnings,
errors
);

// Type check the interface surface.
let mut new_interface_surface = vec![];
for method in interface_surface.into_iter() {
Expand Down Expand Up @@ -73,8 +86,12 @@ impl ty::TyAbiDeclaration {
new_methods.push(ctx.decl_engine.insert(method));
}

// Compared to regular traits, we do not insert recursively methods of ABI supertraits
// into the interface surface, we do not want supertrait methods to be available to
// the ABI user, only the contract methods can use supertrait methods
let abi_decl = ty::TyAbiDeclaration {
interface_surface: new_interface_surface,
supertraits,
methods: new_methods,
name,
span,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,28 @@ impl ty::TyDeclaration {
errors
);
let name = abi_decl.name.clone();

// save decl_refs for the LSP
for supertrait in abi_decl.supertraits.iter_mut() {
ctx.namespace
.resolve_call_path(&supertrait.name)
.cloned()
.map(|supertrait_decl| {
if let ty::TyDeclaration::TraitDeclaration {
name: supertrait_name,
decl_id: supertrait_decl_id,
decl_span: supertrait_decl_span,
} = supertrait_decl
{
supertrait.decl_ref = Some(DeclRef::new(
supertrait_name,
*supertrait_decl_id,
supertrait_decl_span,
));
}
});
}

let decl_ref = decl_engine.insert(abi_decl.clone());
let decl = ty::TyDeclaration::AbiDeclaration {
name: decl_ref.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl ty::TyImplTrait {
&[], // this is empty because abi definitions don't support generics,
&[], // this is empty because abi definitions don't support generics,
&[], // this is empty because abi definitions don't support generics,
&[], // this is empty because abi definitions don't support supertraits,
&abi.supertraits,
&abi.interface_surface,
&abi.methods,
&functions,
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/semantic_analysis/namespace/trait_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,10 +800,10 @@ pub(crate) fn are_equal_minus_dynamic_types(
(TypeInfo::Unknown, TypeInfo::Unknown) => false,
(TypeInfo::SelfType, TypeInfo::SelfType) => false,
(TypeInfo::Numeric, TypeInfo::Numeric) => false,
(TypeInfo::Contract, TypeInfo::Contract) => false,
(TypeInfo::Storage { .. }, TypeInfo::Storage { .. }) => false,

// these cases are able to be directly compared
(TypeInfo::Contract, TypeInfo::Contract) => true,
(TypeInfo::Boolean, TypeInfo::Boolean) => true,
(TypeInfo::B256, TypeInfo::B256) => true,
(TypeInfo::ErrorRecovery, TypeInfo::ErrorRecovery) => true,
Expand Down
4 changes: 4 additions & 0 deletions sway-core/src/semantic_analysis/node_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,12 @@ impl Dependencies {
Declaration::AbiDeclaration(AbiDeclaration {
interface_surface,
methods,
supertraits,
..
}) => self
.gather_from_iter(supertraits.iter(), |deps, sup| {
deps.gather_from_call_path(&sup.name, false, false)
})
.gather_from_iter(interface_surface.iter(), |deps, sig| {
deps.gather_from_iter(sig.parameters.iter(), |deps, param| {
deps.gather_from_type_argument(type_engine, &param.type_argument)
Expand Down
4 changes: 4 additions & 0 deletions sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,10 @@ fn item_abi_to_abi_declaration(
})
.collect::<Result<_, _>>()?
},
supertraits: match item_abi.super_traits {
None => Vec::new(),
Some((_colon_token, traits)) => traits_to_supertraits(context, handler, traits)?,
},
methods: match item_abi.abi_defs_opt {
None => Vec::new(),
Some(abi_defs) => abi_defs
Expand Down
10 changes: 10 additions & 0 deletions sway-lsp/src/traverse/parsed_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,16 @@ impl<'a> ParsedTree<'a> {
self.collect_trait_fn(trait_fn);
}

for supertrait in &abi_decl.supertraits {
self.tokens.insert(
to_ident_key(&supertrait.name.suffix),
Token::from_parsed(
AstToken::Declaration(declaration.clone()),
SymbolKind::Trait,
),
);
}

abi_decl.attributes.parse(self.tokens);
}
Declaration::ConstantDeclaration(const_decl) => {
Expand Down
4 changes: 4 additions & 0 deletions sway-lsp/src/traverse/typed_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ impl<'a> TypedTree<'a> {
self.collect_typed_trait_fn_token(&trait_fn, namespace);
}
}

for supertrait in abi_decl.supertraits {
self.collect_supertrait(&supertrait);
}
}
}
ty::TyDeclaration::GenericTypeForFunctionScope { name, .. } => {
Expand Down
8 changes: 8 additions & 0 deletions sway-parse/src/item/item_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ impl Parse for ItemAbi {
fn parse(parser: &mut Parser) -> ParseResult<ItemAbi> {
let abi_token = parser.parse()?;
let name = parser.parse()?;
let super_traits = match parser.take() {
Some(colon_token) => {
let traits = parser.parse()?;
Some((colon_token, traits))
}
None => None,
};
let abi_items: Braces<Vec<(Annotated<FnSignature>, _)>> = parser.parse()?;
for (fn_signature, _) in abi_items.get().iter() {
parser.ban_visibility_qualifier(&fn_signature.value.visibility)?;
Expand All @@ -20,6 +27,7 @@ impl Parse for ItemAbi {
Ok(ItemAbi {
abi_token,
name,
super_traits,
abi_items,
abi_defs_opt,
})
Expand Down
13 changes: 12 additions & 1 deletion swayfmt/src/items/item_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ impl Format for ItemAbi {
// `abi name`
write!(formatted_code, "{} ", self.abi_token.span().as_str())?;
self.name.format(formatted_code, formatter)?;

// ` : super_trait + super_trait`
if let Some((colon_token, traits)) = &self.super_traits {
write!(formatted_code, " {} ", colon_token.ident().as_str())?;
traits.format(formatted_code, formatter)?;
}

Self::open_curly_brace(formatted_code, formatter)?;

// abi_items
Expand All @@ -37,8 +44,11 @@ impl Format for ItemAbi {
)?;
}

Self::close_curly_brace(formatted_code, formatter)?;

// abi_defs_opt
if let Some(abi_defs) = self.abi_defs_opt.clone() {
Self::open_curly_brace(formatted_code, formatter)?;
for item in abi_defs.get().iter() {
// add indent + format item
write!(
Expand All @@ -48,8 +58,9 @@ impl Format for ItemAbi {
)?;
item.format(formatted_code, formatter)?;
}
writeln!(formatted_code)?;
Self::close_curly_brace(formatted_code, formatter)?;
}
Self::close_curly_brace(formatted_code, formatter)?;

Ok(())
}
Expand Down
Loading

0 comments on commit 8ce3600

Please sign in to comment.