Skip to content

Commit

Permalink
Adds type inference from trait implementations. (#6590)
Browse files Browse the repository at this point in the history
## Description

When we have a type annotation with placeholders and we assign to it the
result of a trait implementation call, if there is a single possible
trait implementation we can use its type in the type annotation type.

Fixes #5299

## 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](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [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.

---------

Co-authored-by: IGI-111 <[email protected]>
Co-authored-by: Joshua Batty <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent 6b05844 commit 34b70f2
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 54 deletions.
45 changes: 45 additions & 0 deletions sway-core/src/semantic_analysis/namespace/trait_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,51 @@ impl TraitMap {
})
}

pub fn get_trait_constraints_are_satisfied_for_types(
&self,
_handler: &Handler,
type_id: TypeId,
constraints: &[TraitConstraint],
engines: &Engines,
) -> Result<Vec<(TypeId, String)>, ErrorEmitted> {
let _decl_engine = engines.de();
let unify_check = UnifyCheck::coercion(engines);
let unify_check_equality = UnifyCheck::non_dynamic_equality(engines);

let impls = self.get_impls(engines, type_id);
let impld_traits_type_ids: Vec<(TypeId, String)> = impls
.iter()
.filter_map(|e| {
let key = &e.key;
let mut res = None;
for constraint in constraints {
if key.name.suffix.name == constraint.trait_name.suffix
&& key
.name
.suffix
.args
.iter()
.zip(constraint.type_arguments.iter())
.all(|(a1, a2)| unify_check_equality.check(a1.type_id, a2.type_id))
&& unify_check.check(type_id, key.type_id)
{
let name_type_args = if !key.name.suffix.args.is_empty() {
format!("<{}>", engines.help_out(key.name.suffix.args.clone()))
} else {
"".to_string()
};
let name = format!("{}{}", key.name.suffix.name.as_str(), name_type_args);
res = Some((key.type_id, name));
break;
}
}
res
})
.collect();

Ok(impld_traits_type_ids)
}

fn get_impls_mut(&mut self, engines: &Engines, type_id: TypeId) -> &mut im::Vector<TraitEntry> {
let type_root_filter = Self::get_type_root_filter(engines, type_id);
if !self.trait_impls.contains_key(&type_root_filter) {
Expand Down
44 changes: 44 additions & 0 deletions sway-core/src/type_system/ast_elements/type_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,50 @@ impl TypeParameter {

let code_block_first_pass = ctx.code_block_first_pass();
if !code_block_first_pass {
// Tries to unify type id with a single existing trait implementation.
// If more than one implementation exists we throw an error.
// We only try to do the type inference from trait with a single trait constraint.
if !type_id.is_concrete(engines, TreatNumericAs::Concrete) && trait_constraints.len() == 1 {
let concrete_trait_type_ids : Vec<(TypeId, String)>= ctx
.namespace_mut()
.module(engines)
.current_items()
.implemented_traits
.get_trait_constraints_are_satisfied_for_types(
handler, *type_id, trait_constraints, engines,
)?
.into_iter()
.filter_map(|t| {
if t.0.is_concrete(engines, TreatNumericAs::Concrete) {
Some(t)
} else {
None
}
}).collect();

match concrete_trait_type_ids.len().cmp(&1) {
Ordering::Equal => {
ctx.engines.te().unify_with_generic(
handler,
engines,
*type_id,
concrete_trait_type_ids.first().unwrap().0,
access_span,
"Type parameter type does not match up with matched trait implementing type.",
None,
);
}
Ordering::Greater => {
return Err(handler.emit_err(CompileError::MultipleImplsSatisfyingTraitForType{
span:access_span.clone(),
type_annotation: engines.help_out(type_id).to_string(),
trait_names: trait_constraints.iter().map(|t| engines.help_out(t).to_string()).collect(),
trait_types_and_names: concrete_trait_type_ids.iter().map(|t| (engines.help_out(t.0).to_string(), t.1.clone())).collect::<Vec<_>>()
}));
}
Ordering::Less => {}
}
}
// Check to see if the trait constraints are satisfied.
match ctx
.namespace_mut()
Expand Down
20 changes: 20 additions & 0 deletions sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,13 @@ pub enum CompileError {
},
#[error("Type must be known at this point")]
TypeMustBeKnownAtThisPoint { span: Span, internal: String },
#[error("Multiple impls satisfying trait for type.")]
MultipleImplsSatisfyingTraitForType {
span: Span,
type_annotation: String,
trait_names: Vec<String>,
trait_types_and_names: Vec<(String, String)>,
},
}

impl std::convert::From<TypeError> for CompileError {
Expand Down Expand Up @@ -1237,6 +1244,7 @@ impl Spanned for CompileError {
ABIHashCollision { span, .. } => span.clone(),
InvalidRangeEndGreaterThanStart { span, .. } => span.clone(),
TypeMustBeKnownAtThisPoint { span, .. } => span.clone(),
MultipleImplsSatisfyingTraitForType { span, .. } => span.clone(),
}
}
}
Expand Down Expand Up @@ -2748,6 +2756,18 @@ impl ToDiagnostic for CompileError {
),
],
},
MultipleImplsSatisfyingTraitForType { span, type_annotation , trait_names, trait_types_and_names: trait_types_and_spans } => Diagnostic {
reason: Some(Reason::new(code(1), format!("Multiple impls satisfying {} for {}", trait_names.join("+"), type_annotation))),
issue: Issue::error(
source_engine,
span.clone(),
String::new()
),
hints: vec![],
help: vec![format!("Trait{} implemented for types:\n{}", if trait_names.len() > 1 {"s"} else {""}, trait_types_and_spans.iter().enumerate().map(|(e, (type_id, name))|
format!("#{} {} for {}", e, name, type_id.clone())
).collect::<Vec<_>>().join("\n"))],
},
_ => Diagnostic {
// TODO: Temporary we use self here to achieve backward compatibility.
// In general, self must not be used and will not be used once we
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = "core"
source = "path+from-root-599A6910FBBA5980"

[[package]]
name = "std"
source = "path+from-root-599A6910FBBA5980"
dependencies = ["core"]

[[package]]
name = "trait_inference_multiple_options"
source = "member"
dependencies = ["std"]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "generic_trait_tuple_without_type_ascription"
name = "trait_inference_multiple_options"
implicit-std = false

[dependencies]
std = { path = "../../../reduced_std_libs/sway-lib-std-assert" }
std = { path = "../../../reduced_std_libs/sway-lib-std-assert" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
script;

pub trait MyFrom<T> {
fn from(b: T) -> Self;
}


pub trait MyInto<T> {
fn into(self) -> T;
}


impl<T, U> MyInto<U> for T
where
U: MyFrom<T>,
{
fn into(self) -> U {
U::from(self)
}
}

impl MyFrom<u256> for (u64, u64, u64, u64) {
fn from(val: u256) -> (u64, u64, u64, u64) {
(42, 0, 0, 0)
}
}

impl MyFrom<u256> for (u64, u64, u64, u32) {
fn from(val: u256) -> (u64, u64, u64, u32) {
(42, 0, 0, 0)
}
}

fn main() -> bool {
let (a, _b, _c, _d) = u256::min().into();

assert_eq(a, 42);

true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
category = "fail"

# check: $()Multiple impls satisfying MyFrom<u256> for (numeric, _, _, _)
# check: $()Trait implemented for types:
# nextln: $()#0 MyFrom<u256> for (u64, u64, u64, u64)
# nextln: $()#1 MyFrom<u256> for (u64, u64, u64, u32)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = "core"
source = "path+from-root-3903213068BE1E4C"

[[package]]
name = "std"
source = "path+from-root-3903213068BE1E4C"
dependencies = ["core"]

[[package]]
name = "trait_inference"
source = "member"
dependencies = ["std"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "trait_inference"

[dependencies]
std = { path = "../../../../reduced_std_libs/sway-lib-std-assert" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
script;

pub trait MyFrom<T> {
fn from(b: T) -> Self;
}


pub trait MyInto<T> {
fn into(self) -> T;
}


impl<T, U> MyInto<U> for T
where
U: MyFrom<T>,
{
fn into(self) -> U {
U::from(self)
}
}

impl MyFrom<u256> for (u64, u64, u64, u64) {
fn from(val: u256) -> (u64, u64, u64, u64) {
(42, 0, 0, 0)
}
}

// Should not interfere with trait inference
impl MyFrom<u64> for (u64, u64, u64, u32) {
fn from(val: u64) -> (u64, u64, u64, u32) {
(42, 0, 0, 0)
}
}

fn main() -> bool {
let (a, _b, _c, _d) = u256::min().into();

assert_eq(a, 42);

true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
category = "run"
expected_result = { action = "return", value = 1 }
expected_result_new_encoding = { action = "return_data", value = "01" }
validate_abi = false
expected_warnings = 3

0 comments on commit 34b70f2

Please sign in to comment.