Skip to content

Commit

Permalink
Fixes generic trait methods not found.
Browse files Browse the repository at this point in the history
The issue this commit solves is the method not found error thrown when
 when we try to use generic traits.

 This issue was fixed by adding support to the `unify_check` so left
 types can match right types `UnknownGeneric`. We also check that the left
 type implemnts the trait constraints of the `UnknownGeneric` before return true.

 Fixes #4806
 Unblocks #4701
  • Loading branch information
esdrubal committed Jul 17, 2023
1 parent 1e66b16 commit fc86c84
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 13 deletions.
2 changes: 1 addition & 1 deletion sway-core/src/semantic_analysis/namespace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub use items::Items;
pub use module::Module;
pub use namespace::Namespace;
pub use root::Root;
pub(super) use trait_map::TraitMap;
pub(crate) use trait_map::TraitMap;

use sway_types::Ident;

Expand Down
19 changes: 14 additions & 5 deletions sway-core/src/semantic_analysis/namespace/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,20 @@ impl Root {
mod_path: &Path,
call_path: &CallPath,
) -> CompileResult<&ty::TyDecl> {
let symbol_path: Vec<_> = mod_path
.iter()
.chain(&call_path.prefixes)
.cloned()
.collect();
let symbol_path: Vec<_> = if call_path.is_absolute {
let mut path = (&call_path.prefixes).clone();
// skip package name when it is the same as root
if path.get(0).cloned() == self.name {
path.remove(0);
}
path
} else {
mod_path
.iter()
.chain(&call_path.prefixes)
.cloned()
.collect()
};
self.resolve_symbol(&symbol_path, &call_path.suffix)
}

Expand Down
9 changes: 5 additions & 4 deletions sway-core/src/semantic_analysis/namespace/trait_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ impl TraitMap {
type_id: TypeId,
) -> Vec<ty::TyTraitItem> {
let type_engine = engines.te();
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let unify_check = UnifyCheck::trait_map_types_subset(engines, &self);

let mut items = vec![];
// small performance gain in bad case
Expand Down Expand Up @@ -747,7 +747,7 @@ impl TraitMap {
/// entries that qualify as hits are equivalents of `type_id`
pub(crate) fn get_impl_spans_for_type(&self, engines: &Engines, type_id: &TypeId) -> Vec<Span> {
let type_engine = engines.te();
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let unify_check = UnifyCheck::trait_map_types_subset(engines, &self);

let mut spans = vec![];
// small performance gain in bad case
Expand Down Expand Up @@ -801,7 +801,7 @@ impl TraitMap {
trait_name: &CallPath,
) -> Vec<ty::TyTraitItem> {
let type_engine = engines.te();
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let unify_check = UnifyCheck::trait_map_types_subset(engines, &self);
let mut items = vec![];
// small performance gain in bad case
if type_engine
Expand Down Expand Up @@ -830,6 +830,7 @@ impl TraitMap {
type_id: TypeId,
) -> Vec<CallPath> {
let type_engine = engines.te();
//cannot use UnifyCheck::trait_map_types_subset as it would give an endless loop because it uses this method
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let mut trait_names = vec![];
// small performance gain in bad case
Expand Down Expand Up @@ -865,7 +866,7 @@ impl TraitMap {

let type_engine = engines.te();
let _decl_engine = engines.de();
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let unify_check = UnifyCheck::trait_map_types_subset(engines, &self);

let all_impld_traits: BTreeMap<Ident, TypeId> = self
.trait_impls
Expand Down
2 changes: 2 additions & 0 deletions sway-core/src/type_system/ast_elements/trait_constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ impl TraitConstraint {
return err(warnings, errors);
}

self.trait_name = self.trait_name.to_fullpath(ctx.namespace);

// Right now we aren't supporting generic traits in trait constraints
// because of how we type check trait constraints.
// Essentially type checking trait constraints with generic traits
Expand Down
48 changes: 45 additions & 3 deletions sway-core/src/type_system/unify/unify_check.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{engine_threading::*, type_system::priv_prelude::*};
use crate::{engine_threading::*, namespace::TraitMap, type_system::priv_prelude::*};
use sway_types::Spanned;

enum UnifyCheckMode {
Expand Down Expand Up @@ -163,38 +163,53 @@ enum UnifyCheckMode {
NonGenericConstraintSubset,

NonDynamicEquality,

TraitMapTypesSubset,
}

/// Helper struct to aid in type coercion.
pub(crate) struct UnifyCheck<'a> {
engines: &'a Engines,
mode: UnifyCheckMode,
trait_map_opt: Option<&'a TraitMap>,
}

impl<'a> UnifyCheck<'a> {
pub(crate) fn coercion(engines: &'a Engines) -> Self {
Self {
engines,
mode: UnifyCheckMode::Coercion,
trait_map_opt: None,
}
}
pub(crate) fn constraint_subset(engines: &'a Engines) -> Self {
Self {
engines,
mode: UnifyCheckMode::ConstraintSubset,
trait_map_opt: None,
}
}
pub(crate) fn non_generic_constraint_subset(engines: &'a Engines) -> Self {
Self {
engines,
mode: UnifyCheckMode::NonGenericConstraintSubset,
trait_map_opt: None,
}
}

pub(crate) fn non_dynamic_equality(engines: &'a Engines) -> Self {
Self {
engines,
mode: UnifyCheckMode::NonDynamicEquality,
trait_map_opt: None,
}
}

pub(crate) fn trait_map_types_subset(engines: &'a Engines, trait_map: &'a TraitMap) -> Self {
Self {
engines,
mode: UnifyCheckMode::TraitMapTypesSubset,
trait_map_opt: Some(trait_map),
}
}

Expand Down Expand Up @@ -292,6 +307,33 @@ impl<'a> UnifyCheck<'a> {
_ => {}
}

if let TraitMapTypesSubset = self.mode {
match (&left_info, &right_info) {
(TypeInfo::UnknownGeneric { .. }, TypeInfo::UnknownGeneric { .. }) => {}
(TypeInfo::Unknown, TypeInfo::UnknownGeneric { .. }) => {}
(TypeInfo::Placeholder(_), TypeInfo::UnknownGeneric { .. }) => {}
(
_,
TypeInfo::UnknownGeneric {
name: _,
trait_constraints,
},
) => {
let left_traits = self
.trait_map_opt
.unwrap()
.get_trait_names_for_type(self.engines, left);
if trait_constraints
.iter()
.all(|constraint| left_traits.contains(&constraint.trait_name))
{
return true;
}
}
_ => {}
}
}

match self.mode {
Coercion => {
match (left_info, right_info) {
Expand Down Expand Up @@ -436,7 +478,7 @@ impl<'a> UnifyCheck<'a> {
(a, b) => a.eq(&b, self.engines),
}
}
NonDynamicEquality => match (left_info, right_info) {
NonDynamicEquality | TraitMapTypesSubset => match (left_info, right_info) {
// when a type alias is encoutered, defer the decision to the type it contains (i.e. the
// type it aliases with)
(Alias { ty, .. }, _) => self.check_inner(ty.type_id, right),
Expand Down Expand Up @@ -651,7 +693,7 @@ impl<'a> UnifyCheck<'a> {
}
}
// no constraint check, just propagate the check
NonDynamicEquality => {}
NonDynamicEquality | TraitMapTypesSubset => {}
}

// if all of the invariants are met, then `self` can be coerced into
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = 'core'
source = 'path+from-root-784776BEEA3D86B0'

[[package]]
name = 'generic_traits_with_bounds'
source = 'member'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-784776BEEA3D86B0'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
name = "generic_traits_with_bounds"
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
implicit-std = false

[dependencies]
std = { path = "../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"functions": [
{
"attributes": null,
"inputs": [],
"name": "main",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
],
"loggedTypes": [],
"types": [
{
"components": null,
"type": "u64",
"typeId": 0,
"typeParameters": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
script;

use std::assert::*;

trait Trait {
fn method(self) -> u64;
}

impl<A, B> Trait for (A, B) where A: Trait, B: Trait {
fn method(self) -> u64 {
self.0.method() + self.1.method()
}
}

/* Without this (1, 2).method() should not be found
impl Trait for u64 {
fn method(self) -> u64 {
self
}
}
*/

fn main() -> bool {
assert((1,2).method() == 3);
true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()assert((1,2).method() == 3);
# nextln: $()No method named "method" found for type "(u64, u64)".
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = 'core'
source = 'path+from-root-AA4B286930BA1707'

[[package]]
name = 'generic_where_in_impl_self'
source = 'member'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-AA4B286930BA1707'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "generic_tuple_trait"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"configurables": [],
"functions": [
{
"attributes": null,
"inputs": [],
"name": "main",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
],
"loggedTypes": [],
"messagesTypes": [],
"types": [
{
"components": null,
"type": "bool",
"typeId": 0,
"typeParameters": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
script;

use std::assert::*;

trait Trait {
fn method(self) -> u64;
}

trait Trait2 {
fn method2(self) -> u64;
}

impl<A, B> Trait for (A, B) {
fn method(self) -> u64 {
42
}
}

impl Trait2 for u64 {
fn method2(self) -> u64 {
self
}
}

impl<A, B> Trait2 for (A, B) where A: Trait2, B: Trait2 {
fn method2(self) -> u64 {
self.0.method2() + self.1.method2()
}
}

fn main() -> bool {
assert((1,2).method() == 42);
assert((1,2).method2() == 3);
true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
category = "run"
expected_result = { action = "return", value = 1 }
validate_abi = true

0 comments on commit fc86c84

Please sign in to comment.