diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs index b9bdfdcae3c..5a26b4539cc 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs @@ -100,7 +100,7 @@ impl TyImplTrait { // Unify the "self" type param and the type that we are implementing for handler.scope(|h| { - type_engine.unify( + type_engine.unify_with_self( h, engines, implementing_for.type_id, @@ -208,7 +208,7 @@ impl TyImplTrait { // Unify the "self" type param from the abi declaration with // the type that we are implementing for. handler.scope(|h| { - type_engine.unify( + type_engine.unify_with_self( h, engines, implementing_for.type_id, @@ -348,7 +348,7 @@ impl TyImplTrait { // Unify the "self" type param and the type that we are implementing for handler.scope(|h| { - type_engine.unify( + type_engine.unify_with_self( h, engines, implementing_for.type_id, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 94906a2d9c7..bb8483403e0 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -386,7 +386,7 @@ fn unify_arguments_and_parameters( for ((_, arg), param) in arguments.iter().zip(parameters.iter()) { // unify the type of the argument with the type of the param let unify_res = handler.scope(|handler| { - type_engine.unify( + type_engine.unify_with_generic( handler, engines, arg.return_type, @@ -574,12 +574,12 @@ pub(crate) fn monomorphize_method_application( // unify method return type with current ctx.type_annotation(). handler.scope(|handler| { - type_engine.unify( + type_engine.unify_with_generic( handler, engines, method.return_type.type_id, ctx.type_annotation(), - &method.return_type.span(), + &call_path.span(), "Function return type does not match up with local type annotation.", None, ); diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs index 8277c13761a..41199cf602d 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs @@ -164,7 +164,8 @@ fn type_check_field_arguments( let ctx = ctx .by_ref() .with_help_text(UNIFY_STRUCT_FIELD_HELP_TEXT) - .with_type_annotation(struct_field.type_argument.type_id); + .with_type_annotation(struct_field.type_argument.type_id) + .with_unify_generic(true); let value = match ty::TyExpression::type_check(handler, ctx, field.value.clone()) { Ok(res) => res, Err(_) => continue, @@ -210,7 +211,7 @@ fn unify_field_arguments_and_struct_fields( handler.scope(|handler| { for struct_field in struct_fields.iter() { if let Some(typed_field) = typed_fields.iter().find(|x| x.name == struct_field.name) { - type_engine.unify( + type_engine.unify_with_generic( handler, engines, typed_field.value.return_type, diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index a62f7cb4354..57b6ee2638a 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -45,6 +45,9 @@ pub struct TypeCheckContext<'a> { /// /// Assists type inference. type_annotation: TypeId, + /// When true unify_with_type_annotation will use unify_with_generic instead of the default unify. + /// This ensures that expected generic types are unified to more specific received types. + unify_generic: bool, /// While type-checking an `impl` (whether inherent or for a `trait`/`abi`) this represents the /// type for which we are implementing. For example in `impl Foo {}` or `impl Trait for Foo /// {}`, this represents the type ID of `Foo`. @@ -102,6 +105,7 @@ impl<'a> TypeCheckContext<'a> { namespace, engines, type_annotation: engines.te().insert(engines, TypeInfo::Unknown), + unify_generic: false, self_type: None, type_subst: TypeSubstMap::new(), help_text: "", @@ -126,6 +130,7 @@ impl<'a> TypeCheckContext<'a> { TypeCheckContext { namespace: self.namespace, type_annotation: self.type_annotation, + unify_generic: self.unify_generic, self_type: self.self_type, type_subst: self.type_subst.clone(), abi_mode: self.abi_mode.clone(), @@ -144,6 +149,7 @@ impl<'a> TypeCheckContext<'a> { TypeCheckContext { namespace, type_annotation: self.type_annotation, + unify_generic: self.unify_generic, self_type: self.self_type, type_subst: self.type_subst, abi_mode: self.abi_mode, @@ -190,6 +196,14 @@ impl<'a> TypeCheckContext<'a> { } } + /// Map this `TypeCheckContext` instance to a new one with the given type annotation. + pub(crate) fn with_unify_generic(self, unify_generic: bool) -> Self { + Self { + unify_generic, + ..self + } + } + /// Map this `TypeCheckContext` instance to a new one with the given type subst. pub(crate) fn with_type_subst(self, type_subst: &TypeSubstMap) -> Self { Self { @@ -267,6 +281,10 @@ impl<'a> TypeCheckContext<'a> { self.type_annotation } + pub(crate) fn unify_generic(&self) -> bool { + self.unify_generic + } + pub(crate) fn self_type(&self) -> Option { self.self_type } @@ -328,15 +346,27 @@ impl<'a> TypeCheckContext<'a> { /// Short-hand around `type_system::unify_`, where the `TypeCheckContext` /// provides the type annotation and help text. pub(crate) fn unify_with_type_annotation(&self, handler: &Handler, ty: TypeId, span: &Span) { - self.engines.te().unify( - handler, - self.engines(), - ty, - self.type_annotation(), - span, - self.help_text(), - None, - ) + if self.unify_generic() { + self.engines.te().unify_with_generic( + handler, + self.engines(), + ty, + self.type_annotation(), + span, + self.help_text(), + None, + ) + } else { + self.engines.te().unify( + handler, + self.engines(), + ty, + self.type_annotation(), + span, + self.help_text(), + None, + ) + } } /// Short-hand for calling [Namespace::insert_symbol] with the `const_shadowing_mode` provided by diff --git a/sway-core/src/type_system/ast_elements/type_parameter.rs b/sway-core/src/type_system/ast_elements/type_parameter.rs index 7765398b117..be6cf9d5571 100644 --- a/sway-core/src/type_system/ast_elements/type_parameter.rs +++ b/sway-core/src/type_system/ast_elements/type_parameter.rs @@ -341,7 +341,7 @@ impl TypeParameter { type_id: sy_type_id, .. }) => { - ctx.engines().te().unify( + ctx.engines().te().unify_with_generic( handler, ctx.engines(), type_parameter.type_id, diff --git a/sway-core/src/type_system/engine.rs b/sway-core/src/type_system/engine.rs index 284dc125edc..2a44782627e 100644 --- a/sway-core/src/type_system/engine.rs +++ b/sway-core/src/type_system/engine.rs @@ -14,6 +14,8 @@ use crate::{ use sway_error::{error::CompileError, type_error::TypeError}; use sway_types::span::Span; +use super::unify::unifier::UnifyKind; + #[derive(Debug, Default)] pub struct TypeEngine { pub(super) slab: ConcurrentSlab, @@ -66,6 +68,64 @@ impl TypeEngine { } } + /// Make the types of `received` and `expected` equivalent (or produce an + /// error if there is a conflict between them). + /// + /// More specifically, this function tries to make `received` equivalent to + /// `expected`. + #[allow(clippy::too_many_arguments)] + pub(crate) fn unify( + &self, + handler: &Handler, + engines: &Engines, + received: TypeId, + expected: TypeId, + span: &Span, + help_text: &str, + err_override: Option, + ) { + self.unify_helper( + handler, + engines, + received, + expected, + span, + help_text, + err_override, + UnifyKind::Default, + ); + } + + /// Make the types of `received` and `expected` equivalent (or produce an + /// error if there is a conflict between them). + /// + /// More specifically, this function tries to make `received` equivalent to + /// `expected`, except in cases where `received` has more type information + /// than `expected` (e.g. when `expected` is a self type and `received` + /// is not). + #[allow(clippy::too_many_arguments)] + pub(crate) fn unify_with_self( + &self, + handler: &Handler, + engines: &Engines, + received: TypeId, + expected: TypeId, + span: &Span, + help_text: &str, + err_override: Option, + ) { + self.unify_helper( + handler, + engines, + received, + expected, + span, + help_text, + err_override, + UnifyKind::WithSelf, + ); + } + /// Make the types of `received` and `expected` equivalent (or produce an /// error if there is a conflict between them). /// @@ -74,7 +134,7 @@ impl TypeEngine { /// than `expected` (e.g. when `expected` is a generic type and `received` /// is not). #[allow(clippy::too_many_arguments)] - pub(crate) fn unify( + pub(crate) fn unify_with_generic( &self, handler: &Handler, engines: &Engines, @@ -83,6 +143,30 @@ impl TypeEngine { span: &Span, help_text: &str, err_override: Option, + ) { + self.unify_helper( + handler, + engines, + received, + expected, + span, + help_text, + err_override, + UnifyKind::WithGeneric, + ); + } + + #[allow(clippy::too_many_arguments)] + fn unify_helper( + &self, + handler: &Handler, + engines: &Engines, + received: TypeId, + expected: TypeId, + span: &Span, + help_text: &str, + err_override: Option, + unify_kind: UnifyKind, ) { if !UnifyCheck::coercion(engines).check(received, expected) { // create a "mismatched type" error unless the `err_override` @@ -103,7 +187,9 @@ impl TypeEngine { return; } let h = Handler::default(); - Unifier::new(engines, help_text).unify(handler, received, expected, span); + let unifier = Unifier::new(engines, help_text, unify_kind); + + unifier.unify(handler, received, expected, span); match err_override { Some(err_override) if h.has_errors() => { handler.emit_err(err_override); diff --git a/sway-core/src/type_system/unify/unifier.rs b/sway-core/src/type_system/unify/unifier.rs index 86ce582d466..144f5538f99 100644 --- a/sway-core/src/type_system/unify/unifier.rs +++ b/sway-core/src/type_system/unify/unifier.rs @@ -7,18 +7,45 @@ use crate::{engine_threading::*, language::ty, type_system::priv_prelude::*}; use super::occurs_check::OccursCheck; +pub(crate) enum UnifyKind { + /// Make the types of `received` and `expected` equivalent (or produce an + /// error if there is a conflict between them). + /// + /// More specifically, this function tries to make `received` equivalent to + /// `expected`. + Default, + /// Make the types of `received` and `expected` equivalent (or produce an + /// error if there is a conflict between them). + /// + /// More specifically, this function tries to make `received` equivalent to + /// `expected`, except in cases where `received` has more type information + /// than `expected` (e.g. when `expected` is a self type and `received` + /// is not). + WithSelf, + /// Make the types of `received` and `expected` equivalent (or produce an + /// error if there is a conflict between them). + /// + /// More specifically, this function tries to make `received` equivalent to + /// `expected`, except in cases where `received` has more type information + /// than `expected` (e.g. when `expected` is a generic type and `received` + /// is not). + WithGeneric, +} + /// Helper struct to aid in type unification. pub(crate) struct Unifier<'a> { engines: &'a Engines, help_text: String, + unify_kind: UnifyKind, } impl<'a> Unifier<'a> { /// Creates a new [Unifier]. - pub(crate) fn new(engines: &'a Engines, help_text: &str) -> Unifier<'a> { + pub(crate) fn new(engines: &'a Engines, help_text: &str, unify_kind: UnifyKind) -> Unifier<'a> { Unifier { engines, help_text: help_text.to_string(), + unify_kind, } } @@ -159,13 +186,23 @@ impl<'a> Unifier<'a> { }, ) if rn.as_str() == en.as_str() && rtc.eq(&etc, self.engines) => (), - (r @ UnknownGeneric { .. }, e) if !self.occurs_check(received, expected) => { + (r @ UnknownGeneric { .. }, e) + if !self.occurs_check(received, expected) + && (matches!(self.unify_kind, UnifyKind::WithGeneric) + || !matches!( + self.engines.te().get(expected), + TypeInfo::UnknownGeneric { .. } + )) => + { self.replace_received_with_expected(handler, received, expected, &r, e, span) } - (r, e @ UnknownGeneric { .. }) if !self.occurs_check(expected, received) => { + (r, e @ UnknownGeneric { .. }) + if !self.occurs_check(expected, received) + && e.is_self_type() + && matches!(self.unify_kind, UnifyKind::WithSelf) => + { self.replace_expected_with_received(handler, received, expected, r, &e, span) } - // Type aliases and the types they encapsulate coerce to each other. (Alias { ty, .. }, _) => self.unify(handler, ty.type_id, expected, span), (_, Alias { ty, .. }) => self.unify(handler, received, ty.type_id, span), diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.lock new file mode 100644 index 00000000000..66eb083490f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "generic_and_associated_type" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.toml new file mode 100644 index 00000000000..8d9796821c4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "generic_and_associated_type" +entry = "main.sw" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/src/main.sw new file mode 100644 index 00000000000..2c4ebfa5bad --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/src/main.sw @@ -0,0 +1,15 @@ +script; + +trait TypeTrait { + type T; + + fn method() -> Self::T; +} { + fn method2() -> T { + Self::method() + } +} + +fn main() -> u32 { + 1 +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/test.toml new file mode 100644 index 00000000000..c6d7130f372 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_and_associated_type/test.toml @@ -0,0 +1,8 @@ +category = "fail" + +# check: $()Self::method() +# nextln: $()Mismatched types. +# nextln: $()expected: T +# nextln: $()found: trait type Self::T +# nextln: $()help: Function return type does not match up with local type annotation. + diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/src/main.sw index 7a450c7f06c..b5baad91e52 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/src/main.sw @@ -111,6 +111,26 @@ fn set_it(mut data: T, new_value: F) where T: Setter { data.set(new_value); } +pub trait MyTrait { + fn my_trait_method() -> Self; +} + +impl MyTrait for U { + fn my_trait_method() -> Self { + 1u64 + } +} + +pub trait MyTrait2 { + fn my_trait_method(t: T) -> Self; +} + +impl MyTrait2 for U { + fn my_trait_method(t: T) -> Self { + t + } +} + fn main() -> u64 { let a = FooBarData { value: 1u8 diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/test.toml index 1c5f20057cd..efc8f8657fc 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generic_traits/test.toml @@ -25,5 +25,19 @@ category = "fail" # check: $()impl Returner for Self { # check: $()"Self" is not valid in the self type of an impl block +# check: $()my_trait_method() -> Self { +# check: $()1u64 +# check: $()Mismatched types. +# check: $()expected: U +# check: $()found: u64. +# check: $()help: Function body's return type does not match up with its return type annotation. + +# check: $()my_trait_method(t: T) -> Self { +# check: $()t +# check: $()Mismatched types. +# check: $()expected: U +# check: $()found: T. +# check: $()help: Function body's return type does not match up with its return type annotation. + # check: $()let b = a.set(42); # check: $()No method named "set" found for type "FooBarData".