From 48104d0bde0d343154a5bc39a310092532883235 Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Wed, 31 May 2023 16:33:24 +0100 Subject: [PATCH] Remove duplicate type checker definitions in favor of `UnifyCheck` (#4593) ## Description This is based on #4184 and fuses all duplicate type subset checkers in favor of using `UnifyCheck` and `TypeInfo` equality. ## 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. Co-authored-by: Marcos Henrich --- .../semantic_analysis/namespace/namespace.rs | 29 +- .../semantic_analysis/namespace/trait_map.rs | 219 +------ sway-core/src/type_system/engine.rs | 2 +- sway-core/src/type_system/info.rs | 324 ---------- sway-core/src/type_system/priv_prelude.rs | 3 +- .../src/type_system/unify/unify_check.rs | 598 +++++++++++++----- 6 files changed, 480 insertions(+), 695 deletions(-) diff --git a/sway-core/src/semantic_analysis/namespace/namespace.rs b/sway-core/src/semantic_analysis/namespace/namespace.rs index 002ca67ae61..096d23856b5 100644 --- a/sway-core/src/semantic_analysis/namespace/namespace.rs +++ b/sway-core/src/semantic_analysis/namespace/namespace.rs @@ -10,10 +10,7 @@ use crate::{ CompileResult, Ident, }; -use super::{ - module::Module, root::Root, submodule_namespace::SubmoduleNamespace, - trait_map::are_equal_minus_dynamic_types, Path, PathBuf, -}; +use super::{module::Module, root::Root, submodule_namespace::SubmoduleNamespace, Path, PathBuf}; use sway_error::error::CompileError; use sway_types::{span::Span, Spanned}; @@ -270,6 +267,8 @@ impl Namespace { let decl_engine = engines.de(); let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); + let matching_item_decl_refs = check!( self.find_items_for_type(type_id, method_prefix, method_name, self_type, engines,), return err(warnings, errors), @@ -294,19 +293,13 @@ impl Namespace { for decl_ref in matching_method_decl_refs.clone().into_iter() { let method = decl_engine.get_function(&decl_ref); if method.parameters.len() == args_buf.len() - && !method.parameters.iter().zip(args_buf.iter()).any(|(p, a)| { - !are_equal_minus_dynamic_types( - engines, - p.type_argument.type_id, - a.return_type, - ) - }) + && method + .parameters + .iter() + .zip(args_buf.iter()) + .all(|(p, a)| unify_check.check(p.type_argument.type_id, a.return_type)) && (matches!(type_engine.get(annotation_type), TypeInfo::Unknown) - || are_equal_minus_dynamic_types( - engines, - annotation_type, - method.return_type.type_id, - )) + || unify_check.check(annotation_type, method.return_type.type_id)) { maybe_method_decl_refs.push(decl_ref); } @@ -356,9 +349,7 @@ impl Namespace { warnings, errors ); - if !are_equal_minus_dynamic_types( - engines, p1_type_id, p2_type_id, - ) { + if !unify_check.check(p1_type_id, p2_type_id) { params_equal = false; break; } diff --git a/sway-core/src/semantic_analysis/namespace/trait_map.rs b/sway-core/src/semantic_analysis/namespace/trait_map.rs index 3bfff852dc1..3b1df998570 100644 --- a/sway-core/src/semantic_analysis/namespace/trait_map.rs +++ b/sway-core/src/semantic_analysis/namespace/trait_map.rs @@ -15,7 +15,7 @@ use crate::{ CallPath, }, type_system::{SubstTypes, TypeId}, - ReplaceSelfType, TraitConstraint, TypeArgument, TypeInfo, TypeSubstMap, + ReplaceSelfType, TraitConstraint, TypeArgument, TypeInfo, TypeSubstMap, UnifyCheck, }; #[derive(Clone, Debug)] @@ -178,12 +178,9 @@ impl TraitMap { }, ); - let types_are_subset = type_engine - .get(type_id) - .is_subset_of(&type_engine.get(*map_type_id), engines); - let traits_are_subset = type_engine - .get(trait_type_id) - .is_subset_of(&type_engine.get(map_trait_type_id), engines); + let unify_checker = UnifyCheck::constraint_subset(engines); + let types_are_subset = unify_checker.check(type_id, *map_type_id); + let traits_are_subset = unify_checker.check(trait_type_id, map_trait_type_id); if types_are_subset && traits_are_subset && !is_impl_self { let trait_name_str = format!( @@ -493,10 +490,11 @@ impl TraitMap { /// with those entries for `Data`. pub(crate) fn filter_by_type(&self, type_id: TypeId, engines: Engines<'_>) -> TraitMap { let type_engine = engines.te(); + + let unify_checker = UnifyCheck::constraint_subset(engines); + // a curried version of the decider protocol to use in the helper functions - let decider = |type_info: &TypeInfo, map_type_info: &TypeInfo| { - type_info.is_subset_of(map_type_info, engines) - }; + let decider = |left: TypeId, right: TypeId| unify_checker.check(left, right); let mut all_types = type_engine.get(type_id).extract_inner_types(engines); all_types.insert(type_id); let all_types = all_types.into_iter().collect::>(); @@ -567,10 +565,13 @@ impl TraitMap { engines: Engines<'_>, ) -> TraitMap { let type_engine = engines.te(); + + let unify_checker = UnifyCheck::constraint_subset(engines); + let unify_checker_for_item_import = UnifyCheck::non_generic_constraint_subset(engines); + // a curried version of the decider protocol to use in the helper functions - let decider = |type_info: &TypeInfo, map_type_info: &TypeInfo| { - type_info.is_subset_of(map_type_info, engines) - || map_type_info.is_subset_of_for_item_import(type_info, engines) + let decider = |left: TypeId, right: TypeId| { + unify_checker.check(left, right) || unify_checker_for_item_import.check(right, left) }; let mut trait_map = self.filter_by_type_inner(engines, vec![type_id], decider); let all_types = type_engine @@ -579,9 +580,8 @@ impl TraitMap { .into_iter() .collect::>(); // a curried version of the decider protocol to use in the helper functions - let decider2 = |type_info: &TypeInfo, map_type_info: &TypeInfo| { - type_info.is_subset_of(map_type_info, engines) - }; + let decider2 = |left: TypeId, right: TypeId| unify_checker.check(left, right); + trait_map.extend( self.filter_by_type_inner(engines, all_types, decider2), engines, @@ -593,7 +593,7 @@ impl TraitMap { &self, engines: Engines<'_>, mut all_types: Vec, - decider: impl Fn(&TypeInfo, &TypeInfo) -> bool, + decider: impl Fn(TypeId, TypeId) -> bool, ) -> TraitMap { let type_engine = engines.te(); let decl_engine = engines.de(); @@ -621,7 +621,7 @@ impl TraitMap { map_trait_items.clone(), engines, ); - } else if decider(&type_info, &type_engine.get(*map_type_id)) { + } else if decider(*type_id, *map_type_id) { let type_mapping = TypeSubstMap::from_superset_and_subset( type_engine, decl_engine, @@ -680,6 +680,8 @@ impl TraitMap { type_id: TypeId, ) -> Vec { let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); + let mut items = vec![]; // small performance gain in bad case if type_engine @@ -689,7 +691,7 @@ impl TraitMap { return items; } for entry in self.trait_impls.iter() { - if are_equal_minus_dynamic_types(engines, type_id, entry.key.type_id) { + if unify_check.check(type_id, entry.key.type_id) { let mut trait_items = entry .value .trait_items @@ -717,6 +719,8 @@ impl TraitMap { type_id: &TypeId, ) -> Vec { let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); + let mut spans = vec![]; // small performance gain in bad case if type_engine @@ -726,7 +730,7 @@ impl TraitMap { return spans; } for entry in self.trait_impls.iter() { - if are_equal_minus_dynamic_types(engines, *type_id, entry.key.type_id) { + if unify_check.check(*type_id, entry.key.type_id) { spans.push(entry.value.impl_span.clone()); } } @@ -769,6 +773,7 @@ impl TraitMap { trait_name: &CallPath, ) -> Vec { let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); let mut items = vec![]; // small performance gain in bad case if type_engine @@ -783,9 +788,7 @@ impl TraitMap { suffix: e.key.name.suffix.name.clone(), is_absolute: e.key.name.is_absolute, }; - if &map_trait_name == trait_name - && are_equal_minus_dynamic_types(engines, type_id, e.key.type_id) - { + if &map_trait_name == trait_name && unify_check.check(type_id, e.key.type_id) { let mut trait_items = e.value.trait_items.values().cloned().collect::>(); items.append(&mut trait_items); } @@ -799,6 +802,7 @@ impl TraitMap { type_id: TypeId, ) -> Vec { let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); let mut trait_names = vec![]; // small performance gain in bad case if type_engine @@ -808,7 +812,7 @@ impl TraitMap { return trait_names; } for entry in self.trait_impls.iter() { - if are_equal_minus_dynamic_types(engines, type_id, entry.key.type_id) { + if unify_check.check(type_id, entry.key.type_id) { let trait_call_path = CallPath { prefixes: entry.key.name.prefixes.clone(), suffix: entry.key.name.suffix.name.clone(), @@ -833,6 +837,7 @@ impl TraitMap { let type_engine = engines.te(); let _decl_engine = engines.de(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); let all_impld_traits: BTreeMap = self .trait_impls @@ -851,7 +856,7 @@ impl TraitMap { }, }, ); - if are_equal_minus_dynamic_types(engines, type_id, key.type_id) { + if unify_check.check(type_id, key.type_id) { Some((suffix.name.clone(), map_trait_type_id)) } else { None @@ -885,11 +890,9 @@ impl TraitMap { .into_iter() .filter(|(impld_trait_name, impld_trait_type_id)| { match required_traits.get(impld_trait_name) { - Some(constraint_type_id) => are_equal_minus_dynamic_types( - engines, - *constraint_type_id, - *impld_trait_type_id, - ), + Some(constraint_type_id) => { + unify_check.check(*constraint_type_id, *impld_trait_type_id) + } _ => false, } }) @@ -915,159 +918,3 @@ impl TraitMap { } } } - -pub(crate) fn are_equal_minus_dynamic_types( - engines: Engines<'_>, - left: TypeId, - right: TypeId, -) -> bool { - if left.index() == right.index() { - return true; - } - - let type_engine = engines.te(); - let decl_engine = engines.de(); - - match (type_engine.get(left), type_engine.get(right)) { - // when a type alias is encoutered, defer the decision to the type it contains (i.e. the - // type it aliases with) - (TypeInfo::Alias { ty, .. }, _) => { - are_equal_minus_dynamic_types(engines, ty.type_id, right) - } - (_, TypeInfo::Alias { ty, .. }) => are_equal_minus_dynamic_types(engines, left, ty.type_id), - - // these cases are false because, unless left and right have the same - // TypeId, they may later resolve to be different types in the type - // engine - (TypeInfo::Unknown, TypeInfo::Unknown) => false, - (TypeInfo::SelfType, TypeInfo::SelfType) => false, - (TypeInfo::Numeric, TypeInfo::Numeric) => 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, - (TypeInfo::Str(l), TypeInfo::Str(r)) => l.val() == r.val(), - (TypeInfo::UnsignedInteger(l), TypeInfo::UnsignedInteger(r)) => l == r, - (TypeInfo::RawUntypedPtr, TypeInfo::RawUntypedPtr) => true, - (TypeInfo::RawUntypedSlice, TypeInfo::RawUntypedSlice) => true, - ( - TypeInfo::UnknownGeneric { - name: rn, - trait_constraints: rtc, - }, - TypeInfo::UnknownGeneric { - name: en, - trait_constraints: etc, - }, - ) => rn.as_str() == en.as_str() && rtc.eq(&etc, engines), - (TypeInfo::Placeholder(_), TypeInfo::Placeholder(_)) => false, - - // these cases may contain dynamic types - ( - TypeInfo::Custom { - call_path: l_name, - type_arguments: l_type_args, - }, - TypeInfo::Custom { - call_path: r_name, - type_arguments: r_type_args, - }, - ) => { - l_name.suffix == r_name.suffix - && l_type_args - .unwrap_or_default() - .iter() - .zip(r_type_args.unwrap_or_default().iter()) - .fold(true, |acc, (left, right)| { - acc && are_equal_minus_dynamic_types(engines, left.type_id, right.type_id) - }) - } - (TypeInfo::Enum(l_decl_ref), TypeInfo::Enum(r_decl_ref)) => { - let l_decl = decl_engine.get_enum(&l_decl_ref); - let r_decl = decl_engine.get_enum(&r_decl_ref); - l_decl.call_path.suffix == r_decl.call_path.suffix - && l_decl.call_path.suffix.span() == r_decl.call_path.suffix.span() - && l_decl.variants.iter().zip(r_decl.variants.iter()).fold( - true, - |acc, (left, right)| { - acc && left.name == right.name - && are_equal_minus_dynamic_types( - engines, - left.type_argument.type_id, - right.type_argument.type_id, - ) - }, - ) - && l_decl - .type_parameters - .iter() - .zip(r_decl.type_parameters.iter()) - .fold(true, |acc, (left, right)| { - acc && left.name_ident == right.name_ident - && are_equal_minus_dynamic_types(engines, left.type_id, right.type_id) - }) - } - (TypeInfo::Struct(l_decl_ref), TypeInfo::Struct(r_decl_ref)) => { - let l_decl = decl_engine.get_struct(&l_decl_ref); - let r_decl = decl_engine.get_struct(&r_decl_ref); - l_decl.call_path.suffix == r_decl.call_path.suffix - && l_decl.call_path.suffix.span() == r_decl.call_path.suffix.span() - && l_decl.fields.iter().zip(r_decl.fields.iter()).fold( - true, - |acc, (left, right)| { - acc && left.name == right.name - && are_equal_minus_dynamic_types( - engines, - left.type_argument.type_id, - right.type_argument.type_id, - ) - }, - ) - && l_decl - .type_parameters - .iter() - .zip(r_decl.type_parameters.iter()) - .fold(true, |acc, (left, right)| { - acc && left.name_ident == right.name_ident - && are_equal_minus_dynamic_types(engines, left.type_id, right.type_id) - }) - } - (TypeInfo::Tuple(l), TypeInfo::Tuple(r)) => { - if l.len() != r.len() { - false - } else { - l.iter().zip(r.iter()).fold(true, |acc, (left, right)| { - acc && are_equal_minus_dynamic_types(engines, left.type_id, right.type_id) - }) - } - } - ( - TypeInfo::ContractCaller { - abi_name: l_abi_name, - address: l_address, - }, - TypeInfo::ContractCaller { - abi_name: r_abi_name, - address: r_address, - }, - ) => { - l_abi_name == r_abi_name - && Option::zip(l_address, r_address) - .map(|(l_address, r_address)| { - are_equal_minus_dynamic_types( - engines, - l_address.return_type, - r_address.return_type, - ) - }) - .unwrap_or(true) - } - (TypeInfo::Array(l0, l1), TypeInfo::Array(r0, r1)) => { - l1.val() == r1.val() && are_equal_minus_dynamic_types(engines, l0.type_id, r0.type_id) - } - _ => false, - } -} diff --git a/sway-core/src/type_system/engine.rs b/sway-core/src/type_system/engine.rs index 89e70e93b56..4fccf291b61 100644 --- a/sway-core/src/type_system/engine.rs +++ b/sway-core/src/type_system/engine.rs @@ -218,7 +218,7 @@ impl TypeEngine { help_text: &str, err_override: Option, ) -> (Vec, Vec) { - if !UnifyCheck::new(engines).check(received, expected) { + if !UnifyCheck::coercion(engines).check(received, expected) { // create a "mismatched type" error unless the `err_override` // argument has been provided let mut errors = vec![]; diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index 5ea70f568b3..025dc8c5ae5 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -1286,223 +1286,6 @@ impl TypeInfo { ) } - /// Given two `TypeInfo`'s `self` and `other`, check to see if `self` is - /// unidirectionally a subset of `other`. - /// - /// `self` is a subset of `other` if it can be generalized over `other`. - /// For example, the generic `T` is a subset of the generic `F` because - /// anything of the type `T` could also be of the type `F` (minus any - /// external context that may make this statement untrue). - /// - /// Given: - /// - /// ```ignore - /// struct Data { - /// x: T, - /// y: F, - /// } - /// ``` - /// - /// the type `Data` is a subset of any generic type. - /// - /// Given: - /// - /// ```ignore - /// struct Data { - /// x: T, - /// y: F, - /// } - /// - /// impl Data { } - /// ``` - /// - /// the type `Data` is a subset of `Data`, but _`Data` is - /// not a subset of `Data`_. - /// - /// Given: - /// - /// ```ignore - /// struct Data { - /// x: T, - /// y: F, - /// } - /// - /// impl Data { } - /// - /// fn dummy() { - /// // the type of foo is Data - /// let foo = Data { - /// x: true, - /// y: 1u64 - /// }; - /// // the type of bar is Data - /// let bar = Data { - /// x: 0u8, - /// y: 0u8 - /// }; - /// } - /// ``` - /// - /// then: - /// - /// | type: | is subset of: | is not a subset of: | - /// |-------------------|----------------------------------------------|---------------------| - /// | `Data` | `Data`, any generic type | | - /// | `Data` | any generic type | `Data` | - /// | `Data` | `Data`, any generic type | `Data` | - /// | `Data` | `Data`, `Data`, any generic type | | - /// - /// For generic types with trait constraints, the generic type `self` is a - /// subset of the generic type `other` when the trait constraints of - /// `other` are a subset of the trait constraints of `self`. This is a bit - /// unintuitive, but you can think of it this way---a generic type `self` - /// can be generalized over `other` when `other` has no methods - /// that `self` doesn't have. These methods are coming from the trait - /// constraints---if the trait constraints of `other` are a subset of the - /// trait constraints of `self`, then we know that `other` has unique - /// methods. - pub(crate) fn is_subset_of(&self, other: &TypeInfo, engines: Engines<'_>) -> bool { - // handle the generics cases - match (self, other) { - ( - Self::UnknownGeneric { - trait_constraints: ltc, - .. - }, - Self::UnknownGeneric { - trait_constraints: rtc, - .. - }, - ) => { - return rtc.eq(ltc, engines); - } - // any type is the subset of a generic - (_, Self::UnknownGeneric { .. }) => { - return true; - } - _ => {} - } - - self.is_subset_inner(other, engines) - } - - /// Given two `TypeInfo`'s `self` and `other`, checks to see if `self` is - /// unidirectionally a subset of `other`, excluding consideration of generic - /// types (like in the `is_subset_of` method). - pub(crate) fn is_subset_of_for_item_import( - &self, - other: &TypeInfo, - engines: Engines<'_>, - ) -> bool { - self.is_subset_inner(other, engines) - } - - fn is_subset_inner(&self, other: &TypeInfo, engines: Engines<'_>) -> bool { - let type_engine = engines.te(); - let decl_engine = engines.de(); - match (self, other) { - (Self::Array(l0, l1), Self::Array(r0, r1)) => { - type_engine - .get(l0.type_id) - .is_subset_of(&type_engine.get(r0.type_id), engines) - && l1.val() == r1.val() - } - ( - Self::Custom { - call_path: l_name, - type_arguments: l_type_args, - }, - Self::Custom { - call_path: r_name, - type_arguments: r_type_args, - }, - ) => { - let l_types = l_type_args - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - let r_types = r_type_args - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - l_name.suffix == r_name.suffix && types_are_subset_of(engines, &l_types, &r_types) - } - (Self::Enum(l_decl_ref), Self::Enum(r_decl_ref)) => { - let l_decl = decl_engine.get_enum(l_decl_ref); - let r_decl = decl_engine.get_enum(r_decl_ref); - let l_names = l_decl - .variants - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let r_names = r_decl - .variants - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let l_types = l_decl - .type_parameters - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - let r_types = r_decl - .type_parameters - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - l_decl_ref.name().clone() == r_decl_ref.name().clone() - && l_names == r_names - && types_are_subset_of(engines, &l_types, &r_types) - } - (Self::Struct(l_decl_ref), Self::Struct(r_decl_ref)) => { - let l_decl = decl_engine.get_struct(l_decl_ref); - let r_decl = decl_engine.get_struct(r_decl_ref); - let l_names = l_decl - .fields - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let r_names = r_decl - .fields - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let l_types = l_decl - .type_parameters - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - let r_types = r_decl - .type_parameters - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - l_decl_ref.name().clone() == r_decl_ref.name().clone() - && l_names == r_names - && types_are_subset_of(engines, &l_types, &r_types) - } - (Self::Tuple(l_types), Self::Tuple(r_types)) => { - let l_types = l_types - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - let r_types = r_types - .iter() - .map(|x| type_engine.get(x.type_id)) - .collect::>(); - types_are_subset_of(engines, &l_types, &r_types) - } - (Self::Alias { ty: l_ty, .. }, Self::Alias { ty: r_ty, .. }) => type_engine - .get(l_ty.type_id) - .is_subset_of(&type_engine.get(r_ty.type_id), engines), - (a, b) => a.eq(b, engines), - } - } - /// Given a `TypeInfo` `self` and a list of `Ident`'s `subfields`, /// iterate through the elements of `subfields` as `subfield`, /// and recursively apply `subfield` to `self`. @@ -1772,113 +1555,6 @@ impl TypeInfo { } } -/// Given two lists of `TypeInfo`'s `left` and `right`, check to see if -/// `left` is a subset of `right`. -/// -/// `left` is a subset of `right` if the following invariants are true: -/// 1. `left` and and `right` are of the same length _n_ -/// 2. For every _i_ in [0, n), `left`ᵢ is a subset of `right`ᵢ -/// 3. The elements of `left` satisfy the trait constraints of `right` -/// -/// A property that falls of out these constraints are that if `left` and -/// `right` are empty, then `left` is a subset of `right`. -/// -/// Given: -/// -/// ```ignore -/// left: [T] -/// right: [T, F] -/// ``` -/// -/// `left` is not a subset of `right` because it violates invariant #1. -/// -/// Given: -/// -/// ```ignore -/// left: [T, F] -/// right: [bool, F] -/// ``` -/// -/// `left` is not a subset of `right` because it violates invariant #2. -/// -/// Given: -/// -/// ```ignore -/// left: [T, F] -/// right: [T, T] -/// ``` -/// -/// `left` is not a subset of `right` because it violates invariant #3. -/// -/// Given: -/// -/// ```ignore -/// left: [T, T] -/// right: [T, F] -/// ``` -/// -/// `left` is a subset of `right`. -/// -/// Given: -/// -/// ```ignore -/// left: [bool, T] -/// right: [T, F] -/// ``` -/// -/// `left` is a subset of `right`. -/// -/// Given: -/// -/// ```ignore -/// left: [Data, Data] -/// right: [Data, Data] -/// ``` -/// -/// `left` is a subset of `right`. -/// -fn types_are_subset_of(engines: Engines<'_>, left: &[TypeInfo], right: &[TypeInfo]) -> bool { - // invariant 1. `left` and and `right` are of the same length _n_ - if left.len() != right.len() { - return false; - } - - // if `left` and `right` are empty, `left` is inherently a subset of `right` - if left.is_empty() && right.is_empty() { - return true; - } - - // invariant 2. For every _i_ in [0, n), `left`ᵢ is a subset of `right`ᵢ - for (l, r) in left.iter().zip(right.iter()) { - if !l.is_subset_of(r, engines) { - return false; - } - } - - // invariant 3. The elements of `left` satisfy the constraints of `right` - let mut constraints = vec![]; - for i in 0..(right.len() - 1) { - for j in (i + 1)..right.len() { - let a = right.get(i).unwrap(); - let b = right.get(j).unwrap(); - if a.eq(b, engines) { - // if a and b are the same type - constraints.push((i, j)); - } - } - } - for (i, j) in constraints.into_iter() { - let a = left.get(i).unwrap(); - let b = left.get(j).unwrap(); - if !a.eq(b, engines) { - return false; - } - } - - // if all of the invariants are met, then `self` is a subset of `other`! - true -} - fn print_inner_types( engines: Engines<'_>, name: String, diff --git a/sway-core/src/type_system/priv_prelude.rs b/sway-core/src/type_system/priv_prelude.rs index 0c3035c1ba1..e081db43eff 100644 --- a/sway-core/src/type_system/priv_prelude.rs +++ b/sway-core/src/type_system/priv_prelude.rs @@ -1,4 +1,4 @@ -pub(super) use super::unify::{unifier::Unifier, unify_check::UnifyCheck}; +pub(super) use super::unify::unifier::Unifier; pub(crate) use super::{ ast_elements::{ @@ -9,6 +9,7 @@ pub(crate) use super::{ engine::{EnforceTypeArguments, MonomorphizeHelper}, info::VecSet, substitute::{subst_list::SubstList, subst_map::TypeSubstMap, subst_types::SubstTypes}, + unify::unify_check::UnifyCheck, }; pub use super::{ diff --git a/sway-core/src/type_system/unify/unify_check.rs b/sway-core/src/type_system/unify/unify_check.rs index bee83b4df4b..a54fa70052a 100644 --- a/sway-core/src/type_system/unify/unify_check.rs +++ b/sway-core/src/type_system/unify/unify_check.rs @@ -1,17 +1,7 @@ use crate::{engine_threading::*, type_system::priv_prelude::*}; use sway_types::Spanned; -/// Helper struct to aid in type coercion. -pub(crate) struct UnifyCheck<'a> { - engines: Engines<'a>, -} - -impl<'a> UnifyCheck<'a> { - /// Creates a new [UnifyCheck]. - pub(crate) fn new(engines: Engines<'a>) -> UnifyCheck<'a> { - UnifyCheck { engines } - } - +enum UnifyCheckMode { /// Given two [TypeId]'s `left` and `right`, check to see if `left` can be /// coerced into `right`. /// @@ -90,106 +80,174 @@ impl<'a> UnifyCheck<'a> { /// constraints---if the trait constraints of `right` can be coerced into /// the trait constraints of `left`, then we know that `right` has unique /// methods. + Coercion, + /// Given two `TypeInfo`'s `self` and `other`, check to see if `self` is + /// unidirectionally a subset of `other`. + /// + /// `self` is a subset of `other` if it can be generalized over `other`. + /// For example, the generic `T` is a subset of the generic `F` because + /// anything of the type `T` could also be of the type `F` (minus any + /// external context that may make this statement untrue). + /// + /// Given: + /// + /// ```ignore + /// struct Data { + /// x: T, + /// y: F, + /// } + /// ``` + /// + /// the type `Data` is a subset of any generic type. + /// + /// Given: + /// + /// ```ignore + /// struct Data { + /// x: T, + /// y: F, + /// } + /// + /// impl Data { } + /// ``` + /// + /// the type `Data` is a subset of `Data`, but _`Data` is + /// not a subset of `Data`_. + /// + /// Given: + /// + /// ```ignore + /// struct Data { + /// x: T, + /// y: F, + /// } + /// + /// impl Data { } + /// + /// fn dummy() { + /// // the type of foo is Data + /// let foo = Data { + /// x: true, + /// y: 1u64 + /// }; + /// // the type of bar is Data + /// let bar = Data { + /// x: 0u8, + /// y: 0u8 + /// }; + /// } + /// ``` + /// + /// then: + /// + /// | type: | is subset of: | is not a subset of: | + /// |-------------------|----------------------------------------------|---------------------| + /// | `Data` | `Data`, any generic type | | + /// | `Data` | any generic type | `Data` | + /// | `Data` | `Data`, any generic type | `Data` | + /// | `Data` | `Data`, `Data`, any generic type | | + /// + /// For generic types with trait constraints, the generic type `self` is a + /// subset of the generic type `other` when the trait constraints of + /// `other` are a subset of the trait constraints of `self`. This is a bit + /// unintuitive, but you can think of it this way---a generic type `self` + /// can be generalized over `other` when `other` has no methods + /// that `self` doesn't have. These methods are coming from the trait + /// constraints---if the trait constraints of `other` are a subset of the + /// trait constraints of `self`, then we know that `other` has unique + /// methods. + ConstraintSubset, + /// Given two `TypeInfo`'s `self` and `other`, checks to see if `self` is + /// unidirectionally a subset of `other`, excluding consideration of generic + /// types. + NonGenericConstraintSubset, + + NonDynamicEquality, +} + +/// Helper struct to aid in type coercion. +pub(crate) struct UnifyCheck<'a> { + engines: Engines<'a>, + mode: UnifyCheckMode, +} + +impl<'a> UnifyCheck<'a> { + pub(crate) fn coercion(engines: Engines<'a>) -> Self { + Self { + engines, + mode: UnifyCheckMode::Coercion, + } + } + pub(crate) fn constraint_subset(engines: Engines<'a>) -> Self { + Self { + engines, + mode: UnifyCheckMode::ConstraintSubset, + } + } + pub(crate) fn non_generic_constraint_subset(engines: Engines<'a>) -> Self { + Self { + engines, + mode: UnifyCheckMode::NonGenericConstraintSubset, + } + } + + pub(crate) fn non_dynamic_equality(engines: Engines<'a>) -> Self { + Self { + engines, + mode: UnifyCheckMode::NonDynamicEquality, + } + } + pub(crate) fn check(&self, left: TypeId, right: TypeId) -> bool { use TypeInfo::*; - + use UnifyCheckMode::*; if left == right { return true; } - let left_info = self.engines.te().get(left); let right_info = self.engines.te().get(right); - match (left_info, right_info) { - // the placeholder type can be coerced into any type - (Placeholder(_), _) => true, - // any type can be coerced into the placeholder type - (_, Placeholder(_)) => true, - - // Type aliases and the types they encapsulate coerce to each other. - (Alias { ty, .. }, _) => self.check(ty.type_id, right), - (_, Alias { ty, .. }) => self.check(left, ty.type_id), - ( - UnknownGeneric { - name: ln, - trait_constraints: ltc, - }, - UnknownGeneric { - name: rn, - trait_constraints: rtc, - }, - ) => { - // TODO: this requirement on the trait constraints should be - // loosened to match the description above - ln == rn && rtc.eq(<c, self.engines) + // override top level generics with simple equality but only at top level + if let NonGenericConstraintSubset = self.mode { + if let UnknownGeneric { .. } = right_info { + return left_info.eq(&right_info, self.engines); } - // any type can be coerced into generic - (_, UnknownGeneric { .. }) => true, - - (Unknown, _) => true, - (_, Unknown) => true, - - (Boolean, Boolean) => true, - (SelfType, SelfType) => true, - (B256, B256) => true, - (Numeric, Numeric) => true, - (Contract, Contract) => true, - (RawUntypedPtr, RawUntypedPtr) => true, - (RawUntypedSlice, RawUntypedSlice) => true, - (UnsignedInteger(_), UnsignedInteger(_)) => true, - (Numeric, UnsignedInteger(_)) => true, - (UnsignedInteger(_), Numeric) => true, - (Str(l), Str(r)) => l.val() == r.val(), + } + self.check_inner(left, right) + } + fn check_inner(&self, left: TypeId, right: TypeId) -> bool { + use TypeInfo::*; + use UnifyCheckMode::*; + + if left == right { + return true; + } + + let left_info = self.engines.te().get(left); + let right_info = self.engines.te().get(right); + + // common recursion patterns + match (&left_info, &right_info) { (Array(l0, l1), Array(r0, r1)) => { - self.check(l0.type_id, r0.type_id) && l1.val() == r1.val() + return self.check_inner(l0.type_id, r0.type_id) && l1.val() == r1.val(); } (Tuple(l_types), Tuple(r_types)) => { let l_types = l_types.iter().map(|x| x.type_id).collect::>(); let r_types = r_types.iter().map(|x| x.type_id).collect::>(); - self.check_multiple(&l_types, &r_types) + return self.check_multiple(&l_types, &r_types); } - ( - Custom { - call_path: l_name, - type_arguments: l_type_args, - }, - Custom { - call_path: r_name, - type_arguments: r_type_args, - }, - ) => { - let l_types = l_type_args - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|x| x.type_id) - .collect::>(); - let r_types = r_type_args - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|x| x.type_id) - .collect::>(); - l_name.suffix == r_name.suffix && self.check_multiple(&l_types, &r_types) - } - // Let empty enums to coerce to any other type. This is useful for Never enum. - (Enum(r_decl_ref), _) - if self.engines.de().get_enum(&r_decl_ref).variants.is_empty() => - { - true - } - (Enum(l_decl_ref), Enum(r_decl_ref)) => { - let l_decl = self.engines.de().get_enum(&l_decl_ref); - let r_decl = self.engines.de().get_enum(&r_decl_ref); + (Struct(l_decl_ref), Struct(r_decl_ref)) => { + let l_decl = self.engines.de().get_struct(l_decl_ref); + let r_decl = self.engines.de().get_struct(r_decl_ref); let l_names = l_decl - .variants + .fields .iter() .map(|x| x.name.clone()) .collect::>(); let r_names = r_decl - .variants + .fields .iter() .map(|x| x.name.clone()) .collect::>(); @@ -203,64 +261,265 @@ impl<'a> UnifyCheck<'a> { .iter() .map(|x| x.type_id) .collect::>(); - l_decl.call_path.suffix == r_decl.call_path.suffix - && l_decl.call_path.suffix.span() == r_decl.call_path.suffix.span() + return l_decl_ref.name().clone() == r_decl_ref.name().clone() && l_names == r_names - && self.check_multiple(&l_types, &r_types) + && self.check_multiple(&l_types, &r_types); } - (Struct(l_decl_ref), Struct(r_decl_ref)) => { - let l_decl = self.engines.de().get_struct(&l_decl_ref); - let r_decl = self.engines.de().get_struct(&r_decl_ref); - let l_names = l_decl - .fields - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let r_names = r_decl - .fields - .iter() - .map(|x| x.name.clone()) - .collect::>(); - let l_types = l_decl - .type_parameters + ( + Custom { + call_path: l_name, + type_arguments: l_type_args, + }, + Custom { + call_path: r_name, + type_arguments: r_type_args, + }, + ) => { + let l_types = l_type_args + .as_ref() + .unwrap_or(&vec![]) .iter() .map(|x| x.type_id) .collect::>(); - let r_types = r_decl - .type_parameters + let r_types = r_type_args + .as_ref() + .unwrap_or(&vec![]) .iter() .map(|x| x.type_id) .collect::>(); - l_decl.call_path.suffix == r_decl.call_path.suffix - && l_decl.call_path.suffix.span() == r_decl.call_path.suffix.span() - && l_names == r_names - && self.check_multiple(&l_types, &r_types) + return l_name.suffix == r_name.suffix && self.check_multiple(&l_types, &r_types); } + _ => {} + } - // For contract callers, they can be coerced if they have the same - // name and at least one has an address of `None` - ( - ref r @ ContractCaller { - abi_name: ref ran, - address: ref ra, - }, - ref e @ ContractCaller { - abi_name: ref ean, - address: ref ea, - }, - ) => { - r.eq(e, self.engines) - || (ran == ean && ra.is_none()) - || matches!(ran, AbiName::Deferred) - || (ran == ean && ea.is_none()) - || matches!(ean, AbiName::Deferred) + match self.mode { + Coercion => { + match (left_info, right_info) { + ( + UnknownGeneric { + name: ln, + trait_constraints: ltc, + }, + UnknownGeneric { + name: rn, + trait_constraints: rtc, + }, + ) => ln == rn && rtc.eq(<c, self.engines), + // any type can be coerced into generic + (_, UnknownGeneric { .. }) => true, + + // Let empty enums to coerce to any other type. This is useful for Never enum. + (Enum(r_decl_ref), _) + if self.engines.de().get_enum(&r_decl_ref).variants.is_empty() => + { + true + } + (Enum(l_decl_ref), Enum(r_decl_ref)) => { + let l_decl = self.engines.de().get_enum(&l_decl_ref); + let r_decl = self.engines.de().get_enum(&r_decl_ref); + let l_names = l_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let r_names = r_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let l_types = l_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + let r_types = r_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + + l_decl_ref.name().clone() == r_decl_ref.name().clone() + && l_names == r_names + && self.check_multiple(&l_types, &r_types) + } + + // the placeholder type can be coerced into any type + (Placeholder(_), _) => true, + // any type can be coerced into the placeholder type + (_, Placeholder(_)) => true, + + // Type aliases and the types they encapsulate coerce to each other. + (Alias { ty, .. }, _) => self.check_inner(ty.type_id, right), + (_, Alias { ty, .. }) => self.check_inner(left, ty.type_id), + + (Unknown, _) => true, + (_, Unknown) => true, + + (UnsignedInteger(_), UnsignedInteger(_)) => true, + (Numeric, UnsignedInteger(_)) => true, + (UnsignedInteger(_), Numeric) => true, + (Str(l), Str(r)) => l.val() == r.val(), + + // For contract callers, they can be coerced if they have the same + // name and at least one has an address of `None` + ( + ref r @ ContractCaller { + abi_name: ref ran, + address: ref ra, + }, + ref e @ ContractCaller { + abi_name: ref ean, + address: ref ea, + }, + ) => { + r.eq(e, self.engines) + || (ran == ean && ra.is_none()) + || matches!(ran, AbiName::Deferred) + || (ran == ean && ea.is_none()) + || matches!(ean, AbiName::Deferred) + } + + (ErrorRecovery, _) => true, + (_, ErrorRecovery) => true, + + (a, b) => a.eq(&b, self.engines), + } + } + ConstraintSubset | NonGenericConstraintSubset => { + match (left_info, right_info) { + ( + UnknownGeneric { + name: _, + trait_constraints: ltc, + }, + UnknownGeneric { + name: _, + trait_constraints: rtc, + }, + ) => rtc.eq(<c, self.engines), + // any type can be coerced into generic + (_, UnknownGeneric { .. }) => true, + + (Enum(l_decl_ref), Enum(r_decl_ref)) => { + let l_decl = self.engines.de().get_enum(&l_decl_ref); + let r_decl = self.engines.de().get_enum(&r_decl_ref); + let l_names = l_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let r_names = r_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let l_types = l_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + let r_types = r_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + + l_decl.call_path.suffix.span() == r_decl.call_path.suffix.span() + && l_decl_ref.name().clone() == r_decl_ref.name().clone() + && l_names == r_names + && self.check_multiple(&l_types, &r_types) + } + + (Alias { ty: l_ty, .. }, Alias { ty: r_ty, .. }) => { + self.check_inner(l_ty.type_id, r_ty.type_id) + } + (a, b) => a.eq(&b, self.engines), + } } + NonDynamicEquality => 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), + (_, Alias { ty, .. }) => self.check_inner(left, ty.type_id), - // this is kinda a hack - (ErrorRecovery, _) => true, - (_, ErrorRecovery) => true, + // these cases are false because, unless left and right have the same + // TypeId, they may later resolve to be different types in the type + // engine + (TypeInfo::Unknown, TypeInfo::Unknown) => false, + (TypeInfo::SelfType, TypeInfo::SelfType) => false, + (TypeInfo::Numeric, TypeInfo::Numeric) => false, + (TypeInfo::Storage { .. }, TypeInfo::Storage { .. }) => false, - (a, b) => a.eq(&b, self.engines), + // 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, + (TypeInfo::Str(l), TypeInfo::Str(r)) => l.val() == r.val(), + (TypeInfo::UnsignedInteger(l), TypeInfo::UnsignedInteger(r)) => l == r, + (TypeInfo::RawUntypedPtr, TypeInfo::RawUntypedPtr) => true, + (TypeInfo::RawUntypedSlice, TypeInfo::RawUntypedSlice) => true, + ( + TypeInfo::UnknownGeneric { + name: rn, + trait_constraints: rtc, + }, + TypeInfo::UnknownGeneric { + name: en, + trait_constraints: etc, + }, + ) => rn.as_str() == en.as_str() && rtc.eq(&etc, self.engines), + (TypeInfo::Placeholder(_), TypeInfo::Placeholder(_)) => false, + + (Enum(l_decl_ref), Enum(r_decl_ref)) => { + let l_decl = self.engines.de().get_enum(&l_decl_ref); + let r_decl = self.engines.de().get_enum(&r_decl_ref); + let l_names = l_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let r_names = r_decl + .variants + .iter() + .map(|x| x.name.clone()) + .collect::>(); + let l_types = l_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + let r_types = r_decl + .type_parameters + .iter() + .map(|x| x.type_id) + .collect::>(); + + l_decl_ref.name().clone() == r_decl_ref.name().clone() + && l_names == r_names + && self.check_multiple(&l_types, &r_types) + } + + ( + TypeInfo::ContractCaller { + abi_name: l_abi_name, + address: l_address, + }, + TypeInfo::ContractCaller { + abi_name: r_abi_name, + address: r_address, + }, + ) => { + l_abi_name == r_abi_name + && Option::zip(l_address, r_address) + .map(|(l_address, r_address)| { + self.check(l_address.return_type, r_address.return_type) + }) + .unwrap_or(true) + } + + _ => false, + }, } } @@ -331,6 +590,7 @@ impl<'a> UnifyCheck<'a> { /// fn check_multiple(&self, left: &[TypeId], right: &[TypeId]) -> bool { use TypeInfo::*; + use UnifyCheckMode::*; // invariant 1. `left` and and `right` are of the same length _n_ if left.len() != right.len() { @@ -345,43 +605,53 @@ impl<'a> UnifyCheck<'a> { // invariant 2. For every _i_ in [0, n), `left`ᵢ can be coerced into // `right`ᵢ for (l, r) in left.iter().zip(right.iter()) { - if !self.check(*l, *r) { + if !self.check_inner(*l, *r) { return false; } } - // invariant 3. The elements of `left` satisfy the constraints of `right` - let left_types = left - .iter() - .map(|x| self.engines.te().get(*x)) - .collect::>(); - let right_types = right - .iter() - .map(|x| self.engines.te().get(*x)) - .collect::>(); - let mut constraints = vec![]; - for i in 0..(right_types.len() - 1) { - for j in (i + 1)..right_types.len() { - let a = right_types.get(i).unwrap(); - let b = right_types.get(j).unwrap(); - if matches!(a, Placeholder(_)) || matches!(b, Placeholder(_)) { - continue; + match self.mode { + Coercion | ConstraintSubset | NonGenericConstraintSubset => { + // invariant 3. The elements of `left` satisfy the constraints of `right` + let left_types = left + .iter() + .map(|x| self.engines.te().get(*x)) + .collect::>(); + let right_types = right + .iter() + .map(|x| self.engines.te().get(*x)) + .collect::>(); + let mut constraints = vec![]; + for i in 0..(right_types.len() - 1) { + for j in (i + 1)..right_types.len() { + let a = right_types.get(i).unwrap(); + let b = right_types.get(j).unwrap(); + if matches!(&self.mode, Coercion) + && (matches!(a, Placeholder(_)) || matches!(b, Placeholder(_))) + { + continue; + } + if a.eq(b, self.engines) { + // if a and b are the same type + constraints.push((i, j)); + } + } } - if a.eq(b, self.engines) { - // if a and b are the same type - constraints.push((i, j)); + for (i, j) in constraints.into_iter() { + let a = left_types.get(i).unwrap(); + let b = left_types.get(j).unwrap(); + if matches!(&self.mode, Coercion) + && (matches!(a, Placeholder(_)) || matches!(b, Placeholder(_))) + { + continue; + } + if !a.eq(b, self.engines) { + return false; + } } } - } - for (i, j) in constraints.into_iter() { - let a = left_types.get(i).unwrap(); - let b = left_types.get(j).unwrap(); - if matches!(a, Placeholder(_)) || matches!(b, Placeholder(_)) { - continue; - } - if !a.eq(b, self.engines) { - return false; - } + // no constraint check, just propagate the check + NonDynamicEquality => {} } // if all of the invariants are met, then `self` can be coerced into