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

SuperABIs for ABIs #4272

Merged
merged 18 commits into from
Jul 27, 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
12 changes: 12 additions & 0 deletions docs/book/src/advanced/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ The implementation of `MyAbi` for `Contract` must also implement the `ABIsupertr

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

### SuperABIs

In addition to supertraits, ABIs can have _superABI_ annotations:

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

The implementation of `MyAbi` for `Contract` must also implement the `MySuperAbi` superABI. Methods in `MySuperAbi` will be part of the `MyAbi` contract interface, i.e. will be available externally (and hence cannot be called from other `MyAbi` contract methods).

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

## Associated Items

Traits can declare different kinds of associated items in their interface surface:
Expand Down
6 changes: 6 additions & 0 deletions examples/abi_superabis/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_superabis"
18 changes: 18 additions & 0 deletions examples/abi_superabis/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
contract;

abi MySuperAbi {
fn foo();
}

abi MyAbi : MySuperAbi {
fn bar();
}

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

// The implementation of MyAbi for Contract must also implement MySuperAbi
impl MyAbi for Contract {
fn bar() {}
}
5 changes: 5 additions & 0 deletions sway-core/src/decl_engine/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ impl<T> DeclId<T> {
pub(crate) fn replace_id(&mut self, index: Self) {
self.0 = index.0;
}

pub(crate) fn dummy() -> Self {
// we assume that `usize::MAX` id is not possible in practice
Self(usize::MAX, PhantomData)
}
}

#[allow(clippy::from_over_into)]
Expand Down
148 changes: 142 additions & 6 deletions sway-core/src/semantic_analysis/ast_node/declaration/abi.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use std::collections::HashSet;

use sway_error::error::CompileError;
use sway_types::{Ident, Spanned};
use sway_types::{Ident, Span, Spanned};

use crate::{
decl_engine::DeclEngineInsert,
decl_engine::{DeclEngineInsert, DeclId},
error::*,
language::{
parsed::*,
ty::{self, TyImplItem, TyTraitItem},
CallPath,
},
semantic_analysis::declaration::SupertraitOf,
semantic_analysis::{
declaration::insert_supertraits_into_namespace, AbiMode, TypeCheckContext,
},
Expand Down Expand Up @@ -45,13 +46,18 @@ impl ty::TyAbiDecl {
let self_type = type_engine.insert(ctx.engines(), TypeInfo::SelfType);
let mut ctx = ctx
.scoped(&mut abi_namespace)
.with_abi_mode(AbiMode::ImplAbiFn)
.with_abi_mode(AbiMode::ImplAbiFn(name.clone(), None))
.with_self_type(self_type);

// Recursively make the interface surfaces and methods of the
// supertraits available to this abi.
check!(
insert_supertraits_into_namespace(ctx.by_ref(), self_type, &supertraits),
insert_supertraits_into_namespace(
ctx.by_ref(),
self_type,
&supertraits,
&SupertraitOf::Abi(span.clone())
),
return err(warnings, errors),
warnings,
errors
Expand All @@ -62,9 +68,42 @@ impl ty::TyAbiDecl {

let mut ids: HashSet<Ident> = HashSet::default();

let error_on_shadowing_superabi_method =
|method_name: &Ident, ctx: &mut TypeCheckContext, errors: &mut Vec<CompileError>| {
if let Some(superabi_impl_method_ref) = ctx
.namespace
.find_method_for_type(
ctx.self_type(),
&[],
&method_name.clone(),
ctx.self_type(),
ctx.type_annotation(),
&Default::default(),
None,
ctx.engines,
false,
)
.value
{
let superabi_impl_method =
ctx.engines.de().get_function(&superabi_impl_method_ref);
if let Some(ty::TyDecl::AbiDecl(abi_decl)) =
superabi_impl_method.implementing_type.clone()
{
errors.push(CompileError::AbiShadowsSuperAbiMethod {
span: method_name.span().clone(),
superabi: abi_decl.name.clone(),
})
}
}
};

for item in interface_surface.into_iter() {
let decl_name = match item {
TraitItem::TraitFn(method) => {
// check that a super-trait does not define a method
// with the same name as the current interface method
error_on_shadowing_superabi_method(&method.name, &mut ctx, &mut errors);
let method = check!(
ty::TyTraitFn::type_check(ctx.by_ref(), method),
return err(warnings, errors),
Expand Down Expand Up @@ -131,6 +170,7 @@ impl ty::TyAbiDecl {
warnings,
errors
);
error_on_shadowing_superabi_method(&method.name, &mut ctx, &mut errors);
for param in &method.parameters {
if param.is_reference || param.is_mutable {
errors.push(CompileError::RefMutableNotAllowedInContractAbi {
Expand Down Expand Up @@ -164,9 +204,13 @@ impl ty::TyAbiDecl {

pub(crate) fn insert_interface_surface_and_items_into_namespace(
&self,
self_decl_id: DeclId<ty::TyAbiDecl>,
ctx: TypeCheckContext,
type_id: TypeId,
) {
subabi_span: Option<Span>,
) -> CompileResult<()> {
let warnings = vec![];
let mut errors = vec![];
let decl_engine = ctx.engines.de();
let engines = ctx.engines();

Expand All @@ -178,15 +222,68 @@ impl ty::TyAbiDecl {

let mut all_items = vec![];

let (look_for_conflicting_abi_methods, subabi_span) = if let Some(subabi) = subabi_span {
(true, subabi)
} else {
(false, Span::dummy())
};

for item in interface_surface.iter() {
match item {
ty::TyTraitInterfaceItem::TraitFn(decl_ref) => {
let mut method = decl_engine.get_trait_fn(decl_ref);
if look_for_conflicting_abi_methods {
// looking for conflicting ABI methods for triangle-like ABI hierarchies
if let Some(superabi_method_ref) = ctx
.namespace
.find_method_for_type(
ctx.self_type(),
&[],
&method.name.clone(),
ctx.self_type(),
ctx.type_annotation(),
&Default::default(),
None,
ctx.engines,
false,
)
.value
{
let superabi_method =
ctx.engines.de().get_function(&superabi_method_ref);
if let Some(ty::TyDecl::AbiDecl(abi_decl)) =
superabi_method.implementing_type.clone()
{
// rule out the diamond superABI hierarchy:
// it's not an error if the "conflicting" methods
// actually come from the same super-ABI
// Top
// / \
// Left Right
// \ /
// Bottom
// if we are accumulating methods from Left and Right
// to place it into Bottom we will encounter
// the same method from Top in both Left and Right
if self_decl_id != abi_decl.decl_id {
errors.push(CompileError::ConflictingSuperAbiMethods {
span: subabi_span.clone(),
method_name: method.name.to_string(),
superabi1: abi_decl.name.to_string(),
superabi2: self.name.to_string(),
})
}
}
}
}
method.replace_self_type(engines, type_id);
all_items.push(TyImplItem::Fn(
ctx.engines
.de()
.insert(method.to_dummy_func(AbiMode::ImplAbiFn))
.insert(method.to_dummy_func(AbiMode::ImplAbiFn(
self.name.clone(),
Some(self_decl_id),
)))
.with_parent(ctx.engines.de(), (*decl_ref.id()).into()),
));
}
Expand All @@ -211,6 +308,40 @@ impl ty::TyAbiDecl {
match item {
ty::TyTraitItem::Fn(decl_ref) => {
let mut method = decl_engine.get_function(decl_ref);
// check if we inherit the same impl method from different branches
// XXX this piece of code can be abstracted out into a closure
// and reused for interface methods if the issue of mutable ctx is solved
if let Some(superabi_impl_method_ref) = ctx
.namespace
.find_method_for_type(
ctx.self_type(),
&[],
&method.name.clone(),
ctx.self_type(),
ctx.type_annotation(),
&Default::default(),
None,
ctx.engines,
false,
)
.value
{
let superabi_impl_method =
ctx.engines.de().get_function(&superabi_impl_method_ref);
if let Some(ty::TyDecl::AbiDecl(abi_decl)) =
superabi_impl_method.implementing_type.clone()
{
// allow the diamond superABI hierarchy
if self_decl_id != abi_decl.decl_id {
errors.push(CompileError::ConflictingSuperAbiMethods {
span: subabi_span.clone(),
method_name: method.name.to_string(),
superabi1: abi_decl.name.to_string(),
superabi2: self.name.to_string(),
})
}
}
}
method.replace_self_type(engines, type_id);
all_items.push(TyImplItem::Fn(
ctx.engines
Expand Down Expand Up @@ -242,5 +373,10 @@ impl ty::TyAbiDecl {
false,
ctx.engines,
);
if errors.is_empty() {
ok((), warnings, errors)
} else {
err(warnings, errors)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,28 @@ impl ty::TyDecl {
warnings,
errors
);
// if this ImplTrait implements a trait and not an ABI,
// we insert its methods into the context
// otherwise, if it implements an ABI, we do not
// insert those since we do not allow calling contract methods
// from contract methods
let emp_vec = vec![];
let impl_trait_items = if let Some(ty::TyDecl::TraitDecl { .. }) = ctx
.namespace
.resolve_call_path(&impl_trait.trait_name)
.ok(&mut warnings, &mut errors)
.cloned()
{
&impl_trait.items
} else {
&emp_vec
};
check!(
ctx.namespace.insert_trait_implementation(
impl_trait.trait_name.clone(),
impl_trait.trait_type_arguments.clone(),
impl_trait.implementing_for.type_id,
&impl_trait.items,
impl_trait_items,
&impl_trait.span,
impl_trait
.trait_decl_ref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl ty::TyFunctionDecl {
(Visibility::Public, false)
}
} else {
(visibility, ctx.abi_mode() == AbiMode::ImplAbiFn)
(visibility, matches!(ctx.abi_mode(), AbiMode::ImplAbiFn(..)))
};

check!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,15 @@ impl ty::TyImplTrait {
});
}

let mut ctx = ctx.with_abi_mode(AbiMode::ImplAbiFn);
let mut ctx = ctx.with_abi_mode(AbiMode::ImplAbiFn(abi.name.clone(), None));

// Insert the interface surface and methods from this trait into
// the namespace.
abi.insert_interface_surface_and_items_into_namespace(
decl_id,
ctx.by_ref(),
implementing_for.type_id,
None,
);

let new_items = check!(
Expand Down Expand Up @@ -480,7 +482,6 @@ fn type_check_trait_implementation(
warnings,
errors
);

ctx.namespace.insert_trait_implementation(
trait_name.clone(),
trait_type_arguments.to_vec(),
Expand Down Expand Up @@ -1108,9 +1109,9 @@ fn handle_supertraits(
interface_surface_item_ids.extend(next_interface_supertrait_decl_refs);
impld_item_refs.extend(next_these_supertrait_decl_refs);
}
Some(ty::TyDecl::AbiDecl { .. }) => errors.push(CompileError::AbiAsSupertrait {
span: supertrait.name.span().clone(),
}),
Some(ty::TyDecl::AbiDecl { .. }) => {
// we allow ABIs as superABIs now
}
_ => errors.push(CompileError::TraitNotFound {
name: supertrait.name.to_string(),
span: supertrait.name.span(),
Expand Down
Loading