diff --git a/chalk-engine/src/logic.rs b/chalk-engine/src/logic.rs index e7da77ad113..d176c46c129 100644 --- a/chalk-engine/src/logic.rs +++ b/chalk-engine/src/logic.rs @@ -13,8 +13,8 @@ use crate::{ use chalk_ir::interner::Interner; use chalk_ir::{debug, debug_heading, info, info_heading}; use chalk_ir::{ - Canonical, ConstrainedSubst, Floundered, Goal, GoalData, InEnvironment, NoSolution, - Substitution, UCanonical, UniverseMap, + Canonical, ConstrainedSubst, DomainGoal, Floundered, Goal, GoalData, InEnvironment, NoSolution, + Substitution, UCanonical, UniverseMap, WhereClause, }; type RootSearchResult = Result; @@ -251,8 +251,18 @@ impl> Forest { ) -> Table { let mut table = Table::new(goal.clone(), context.is_coinductive(&goal)); let (mut infer, subst, environment, goal) = context.instantiate_ucanonical_goal(&goal); - match goal.data(context.interner()) { - GoalData::DomainGoal(domain_goal) => { + let goal_data = goal.data(context.interner()); + + let is_outlives_goal = |dg: &DomainGoal| { + if let DomainGoal::Holds(WhereClause::LifetimeOutlives(_)) = dg { + true + } else { + false + } + }; + + match goal_data { + GoalData::DomainGoal(domain_goal) if !is_outlives_goal(domain_goal) => { match context.program_clauses(&environment, &domain_goal, &mut infer) { Ok(clauses) => { for clause in clauses { diff --git a/chalk-integration/src/lowering.rs b/chalk-integration/src/lowering.rs index f4c81f51eaa..86c11fa4302 100644 --- a/chalk-integration/src/lowering.rs +++ b/chalk-integration/src/lowering.rs @@ -1827,6 +1827,7 @@ impl LowerWellKnownTrait for WellKnownTrait { Self::FnOnce => rust_ir::WellKnownTrait::FnOnce, Self::FnMut => rust_ir::WellKnownTrait::FnMut, Self::Fn => rust_ir::WellKnownTrait::Fn, + Self::Unsize => rust_ir::WellKnownTrait::Unsize, } } } diff --git a/chalk-ir/src/lib.rs b/chalk-ir/src/lib.rs index 890b9f0b592..ace0a54643d 100644 --- a/chalk-ir/src/lib.rs +++ b/chalk-ir/src/lib.rs @@ -1351,6 +1351,15 @@ impl WhereClause { wc => wc.cast(interner), } } + + /// If where clause is a `TraitRef`, returns its trait id + pub fn trait_id(&self) -> Option> { + match self { + WhereClause::Implemented(trait_ref) => Some(trait_ref.trait_id), + WhereClause::AliasEq(_) => None, + WhereClause::LifetimeOutlives(_) => None, + } + } } impl QuantifiedWhereClause { @@ -1369,6 +1378,11 @@ impl QuantifiedWhereClause { pub fn into_from_env_goal(self, interner: &I) -> Binders> { self.map(|wc| wc.into_from_env_goal(interner)) } + + /// If the underlying where clause is a `TraitRef`, returns its trait id + pub fn trait_id(&self) -> Option> { + self.skip_binders().trait_id() + } } #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, HasInterner)] diff --git a/chalk-parse/src/ast.rs b/chalk-parse/src/ast.rs index 62ec229cad7..29992e28b3c 100644 --- a/chalk-parse/src/ast.rs +++ b/chalk-parse/src/ast.rs @@ -71,6 +71,7 @@ pub enum WellKnownTrait { FnOnce, FnMut, Fn, + Unsize, } #[derive(Clone, PartialEq, Eq, Debug)] diff --git a/chalk-parse/src/parser.lalrpop b/chalk-parse/src/parser.lalrpop index 6e7756b72a5..1ab6369aca6 100644 --- a/chalk-parse/src/parser.lalrpop +++ b/chalk-parse/src/parser.lalrpop @@ -54,6 +54,7 @@ WellKnownTrait: WellKnownTrait = { "#" "[" "lang" "(" "fn_once" ")" "]" => WellKnownTrait::FnOnce, "#" "[" "lang" "(" "fn_mut" ")" "]" => WellKnownTrait::FnMut, "#" "[" "lang" "(" "fn" ")" "]" => WellKnownTrait::Fn, + "#" "[" "lang" "(" "unsize" ")" "]" => WellKnownTrait::Unsize, }; StructDefn: StructDefn = { diff --git a/chalk-solve/src/clauses/builtin_traits.rs b/chalk-solve/src/clauses/builtin_traits.rs index 4a4259695cd..98392bfa083 100644 --- a/chalk-solve/src/clauses/builtin_traits.rs +++ b/chalk-solve/src/clauses/builtin_traits.rs @@ -6,6 +6,7 @@ mod clone; mod copy; mod fn_family; mod sized; +mod unsize; /// For well known traits we have special hard-coded impls, either as an /// optimization or to enforce special rules for correctness. @@ -36,6 +37,9 @@ pub fn add_builtin_program_clauses( WellKnownTrait::FnOnce | WellKnownTrait::FnMut | WellKnownTrait::Fn => { fn_family::add_fn_trait_program_clauses(db, builder, trait_ref.trait_id, self_ty)? } + WellKnownTrait::Unsize => { + unsize::add_unsize_program_clauses(db, builder, &trait_ref, ty) + } // Drop impls are provided explicitly WellKnownTrait::Drop => (), } diff --git a/chalk-solve/src/clauses/builtin_traits/unsize.rs b/chalk-solve/src/clauses/builtin_traits/unsize.rs new file mode 100644 index 00000000000..df1fd099411 --- /dev/null +++ b/chalk-solve/src/clauses/builtin_traits/unsize.rs @@ -0,0 +1,498 @@ +use std::collections::HashSet; +use std::iter; + +use crate::clauses::ClauseBuilder; +use crate::{Interner, RustIrDatabase, TraitRef, WellKnownTrait}; +use chalk_ir::{ + cast::Cast, + interner::HasInterner, + visit::{visitors::FindAny, SuperVisit, Visit, VisitResult, Visitor}, + ApplicationTy, Binders, Const, ConstValue, DebruijnIndex, DomainGoal, DynTy, EqGoal, Goal, + LifetimeOutlives, QuantifiedWhereClauses, Substitution, TraitId, Ty, TyData, TypeName, + WhereClause, +}; + +struct UnsizeParameterCollector<'a, I: Interner> { + interner: &'a I, + // FIXME should probably use a bitset instead + parameters: HashSet, +} + +impl<'a, I: Interner> Visitor<'a, I> for UnsizeParameterCollector<'a, I> { + type Result = (); + + fn as_dyn(&mut self) -> &mut dyn Visitor<'a, I, Result = Self::Result> { + self + } + + fn visit_ty(&mut self, ty: &Ty, outer_binder: DebruijnIndex) -> Self::Result { + let interner = self.interner; + + match ty.data(interner) { + TyData::BoundVar(bound_var) => { + // check if bound var refers to the outermost binder + if bound_var.debruijn.shifted_in() == outer_binder { + self.parameters.insert(bound_var.index); + } + } + _ => ty.super_visit_with(self, outer_binder), + } + } + + fn visit_const(&mut self, constant: &Const, outer_binder: DebruijnIndex) -> Self::Result { + let interner = self.interner; + + match constant.data(interner).value { + ConstValue::BoundVar(bound_var) => { + // check if bound var refers to the outermost binder + if bound_var.debruijn.shifted_in() == outer_binder { + self.parameters.insert(bound_var.index); + } + } + _ => (), + } + } + + fn interner(&self) -> &'a I { + self.interner + } +} + +fn outer_binder_parameters_used( + interner: &I, + v: &Binders + HasInterner>, +) -> HashSet { + let mut visitor = UnsizeParameterCollector { + interner, + parameters: HashSet::new(), + }; + v.visit_with(&mut visitor, DebruijnIndex::INNERMOST); + visitor.parameters +} + +// has nothing to do with occurs check +struct ParameterOccurenceCheck<'a, 'p, I: Interner> { + interner: &'a I, + parameters: &'p HashSet, +} + +impl<'a, 'p, I: Interner> Visitor<'a, I> for ParameterOccurenceCheck<'a, 'p, I> { + type Result = FindAny; + + fn as_dyn(&mut self) -> &mut dyn Visitor<'a, I, Result = Self::Result> { + self + } + + fn visit_ty(&mut self, ty: &Ty, outer_binder: DebruijnIndex) -> Self::Result { + let interner = self.interner; + + match ty.data(interner) { + TyData::BoundVar(bound_var) => { + if bound_var.debruijn.shifted_in() == outer_binder + && self.parameters.contains(&bound_var.index) + { + FindAny::FOUND + } else { + FindAny::new() + } + } + _ => ty.super_visit_with(self, outer_binder), + } + } + + fn visit_const(&mut self, constant: &Const, outer_binder: DebruijnIndex) -> Self::Result { + let interner = self.interner; + + match constant.data(interner).value { + ConstValue::BoundVar(bound_var) => { + if bound_var.debruijn.shifted_in() == outer_binder + && self.parameters.contains(&bound_var.index) + { + FindAny::FOUND + } else { + FindAny::new() + } + } + _ => FindAny::new(), + } + } + + fn interner(&self) -> &'a I { + self.interner + } +} + +fn uses_outer_binder_params( + interner: &I, + v: &Binders + HasInterner>, + parameters: &HashSet, +) -> bool { + let mut visitor = ParameterOccurenceCheck { + interner, + parameters, + }; + v.visit_with(&mut visitor, DebruijnIndex::INNERMOST) == FindAny::FOUND +} + +fn principal_id<'a, I: Interner>( + db: &dyn RustIrDatabase, + bounds: &'a Binders>, +) -> Option> { + let interner = db.interner(); + + return bounds + .skip_binders() + .iter(interner) + .filter_map(|b| b.trait_id()) + .filter(|&id| !db.trait_datum(id).is_auto_trait()) + .next(); +} + +fn auto_trait_ids<'a, I: Interner>( + db: &'a dyn RustIrDatabase, + bounds: &'a Binders>, +) -> impl Iterator> + 'a { + let interner = db.interner(); + + bounds + .skip_binders() + .iter(interner) + .filter_map(|clause| clause.trait_id()) + .filter(move |&id| db.trait_datum(id).is_auto_trait()) +} + +pub fn add_unsize_program_clauses( + db: &dyn RustIrDatabase, + builder: &mut ClauseBuilder<'_, I>, + trait_ref: &TraitRef, + _ty: &TyData, +) { + let interner = db.interner(); + + let source_ty = trait_ref.self_type_parameter(interner); + let target_ty = trait_ref + .substitution + .at(interner, 1) + .assert_ty_ref(interner); + + let unsize_trait_id = trait_ref.trait_id; + + // N.B. here rustc asserts that `TraitRef` is not a higher-ranked bound + // i.e. `for<'a> &'a T: Unsize` is never provable. + // + // In chalk it would be awkward to implement and I am not sure + // there is a need for it, the original comment states that this restriction + // could be lifted. + // + // for more info visit `fn assemble_candidates_for_unsizing` and + // `fn confirm_builtin_unisize_candidate` in rustc. + + match (source_ty.data(interner), target_ty.data(interner)) { + // dyn Trait + AutoX + 'a -> dyn Trait + AutoY + 'b + ( + TyData::Dyn(DynTy { + bounds: bounds_a, + lifetime: lifetime_a, + }), + TyData::Dyn(DynTy { + bounds: bounds_b, + lifetime: lifetime_b, + }), + ) => { + let principal_a = principal_id(db, bounds_a); + let principal_b = principal_id(db, bounds_b); + + let auto_trait_ids_a: Vec<_> = auto_trait_ids(db, bounds_a).collect(); + let auto_trait_ids_b: Vec<_> = auto_trait_ids(db, bounds_b).collect(); + + let may_apply = principal_a == principal_b + && auto_trait_ids_b + .iter() + .all(|id_b| auto_trait_ids_a.iter().any(|id_a| id_a == id_b)); + + if !may_apply { + return; + } + + // COMMENT FROM RUSTC: + // ------------------ + // Require that the traits involved in this upcast are **equal**; + // only the **lifetime bound** is changed. + // + // This condition is arguably too strong -- it would + // suffice for the source trait to be a *subtype* of the target + // trait. In particular, changing from something like + // `for<'a, 'b> Foo<'a, 'b>` to `for<'a> Foo<'a, 'a>` should be + // permitted. + // <...> + // I've modified this to `.eq` because I want to continue rejecting + // that [`old-lub-glb-object.rs`] test (as we have + // done for quite some time) before we are firmly comfortable + // with what our behavior should be there. -nikomatsakis + // ------------------ + + // Construct a new trait object type by taking the source ty, + // filtering out auto traits of source that are not present in target + // and changing source lifetime to target lifetime. + // + // In order for the coercion to be valid, this new type + // should be equal to target type. + let new_source_ty = TyData::Dyn(DynTy { + bounds: bounds_a.map_ref(|bounds| { + QuantifiedWhereClauses::from( + interner, + bounds.iter(interner).filter(|bound| { + let trait_id = match bound.trait_id() { + Some(id) => id, + None => return true, + }; + + if auto_trait_ids_a.iter().all(|&id_a| id_a != trait_id) { + return true; + } + auto_trait_ids_b.iter().any(|&id_b| id_b == trait_id) + }), + ) + }), + lifetime: lifetime_b.clone(), + }) + .intern(interner); + + // Check that new source is equal to target + let eq_goal = EqGoal { + a: new_source_ty.cast(interner), + b: target_ty.clone().cast(interner), + } + .cast(interner); + + // Check that source lifetime outlives target lifetime + let lifetime_outlives_goal: Goal = WhereClause::LifetimeOutlives(LifetimeOutlives { + a: lifetime_a.clone(), + b: lifetime_b.clone(), + }) + .cast(interner); + + builder.push_clause(trait_ref.clone(), [eq_goal, lifetime_outlives_goal].iter()); + } + + // T -> dyn Trait + 'a + ( + _, + TyData::Dyn(DynTy { + bounds, + lifetime: _, + }), + ) => { + // Check if all traits in trait object are object safe + let object_safe_goals = bounds + .skip_binders() + .iter(interner) + .filter_map(|bound| bound.trait_id()) + .map(|id| DomainGoal::ObjectSafe(id).cast(interner)); + + // Check that T implements all traits of the trait object + let source_ty_bounds = + bounds.substitute(interner, &Substitution::from1(interner, source_ty.clone())); + + // Check that T is sized because we can only make + // a trait object from a sized type + let self_sized_goal: WhereClause<_> = TraitRef { + trait_id: db + .well_known_trait_id(WellKnownTrait::Sized) + .expect("Expected Sized to be defined when proving Unsize"), + substitution: Substitution::from1(interner, source_ty.clone()), + } + .cast(interner); + + // FIXME(areredify) we need a `source_ty: 'lifetime` goal here + + builder.push_clause( + trait_ref.clone(), + source_ty_bounds + .iter(interner) + .map(|bound| bound.clone().cast::>(interner)) + .chain(object_safe_goals) + .chain(iter::once(self_sized_goal.cast(interner))), + ); + } + + ( + TyData::Apply(ApplicationTy { + name: TypeName::Array, + substitution: array_subst, + }), + TyData::Apply(ApplicationTy { + name: TypeName::Slice, + substitution: slice_subst, + }), + ) => { + let array_ty = array_subst.at(interner, 0); + let slice_ty = slice_subst.at(interner, 0); + + let eq_goal = EqGoal { + a: array_ty.clone(), + b: slice_ty.clone(), + }; + + builder.push_clause(trait_ref.clone(), iter::once(eq_goal)); + } + + // Struct -> Struct + // Unsizing of enums is not allowed + ( + TyData::Apply(ApplicationTy { + name: TypeName::Adt(struct_id_a), + substitution: substitution_a, + }), + TyData::Apply(ApplicationTy { + name: TypeName::Adt(struct_id_b), + substitution: substitution_b, + }), + ) => { + if struct_id_a != struct_id_b { + return; + } + + let struct_id = *struct_id_a; + let struct_datum = db.adt_datum(struct_id); + let fields_len = struct_datum.binders.skip_binders().fields.len(); + + if fields_len == 0 { + return; + } + + let adt_tail_field = struct_datum + .binders + .map_ref(|bound| bound.fields.last().unwrap()); + + // Collect unsize parameters that last field contains and + // ensure there at least one of them. + let unsize_parameter_candidates = + outer_binder_parameters_used(interner, &adt_tail_field); + + if unsize_parameter_candidates.len() == 0 { + return; + } + // Ensure none of the other fields mention the parameters used + // in unsizing. + // We specifically want variables specified by the outermost binder + // i.e. the struct generic arguments binder. + if uses_outer_binder_params( + interner, + &struct_datum + .binders + .map_ref(|bound| &bound.fields[..fields_len - 1]), + &unsize_parameter_candidates, + ) { + return; + } + + let parameters_a = substitution_a.parameters(interner); + let parameters_b = substitution_b.parameters(interner); + // Check that the source adt with the target's + // unsizing parameters is equal to the target. + // We construct a new substitution where if a parameter is used in the + // coercion (i.e. it's a non-lifetime struct parameter used by it's last field), + // then we take that parameter from target substitution, otherwise we take + // it from the source substitution. + // + // In order for the coercion to be valid, target struct and + // struct with this newly constructed substitution applied to it should be equal. + let substitution = Substitution::from( + interner, + parameters_a.iter().enumerate().map(|(i, p)| { + if unsize_parameter_candidates.contains(&i) { + ¶meters_b[i] + } else { + p + } + }), + ); + + let eq_goal = EqGoal { + a: TyData::Apply(ApplicationTy { + name: TypeName::Adt(struct_id), + substitution, + }) + .intern(interner) + .cast(interner), + b: target_ty.clone().cast(interner), + } + .cast(interner); + + // Extract `TailField` and `TailField` from `Struct` and `Struct`. + let source_tail_field = adt_tail_field.substitute(interner, substitution_a); + let target_tail_field = adt_tail_field.substitute(interner, substitution_b); + + // Check that `TailField: Unsize>` + let last_field_unsizing_goal: Goal = TraitRef { + trait_id: unsize_trait_id, + substitution: Substitution::from( + interner, + [source_tail_field, target_tail_field].iter().cloned(), + ), + } + .cast(interner); + + builder.push_clause( + trait_ref.clone(), + [eq_goal, last_field_unsizing_goal].iter(), + ); + } + + // (.., T) -> (.., U) + ( + TyData::Apply(ApplicationTy { + name: TypeName::Tuple(arity_a), + substitution: substitution_a, + }), + TyData::Apply(ApplicationTy { + name: TypeName::Tuple(arity_b), + substitution: substitution_b, + }), + ) => { + if arity_a != arity_b || *arity_a == 0 { + return; + } + let arity = arity_a; + + let tail_ty_a = substitution_a.iter(interner).last().unwrap(); + let tail_ty_b = substitution_b.iter(interner).last().unwrap(); + + // Check that the source tuple with the target's + // last element is equal to the target. + let new_tuple = ApplicationTy { + name: TypeName::Tuple(*arity), + substitution: Substitution::from( + interner, + substitution_a + .iter(interner) + .take(arity - 1) + .chain(iter::once(tail_ty_b)), + ), + } + .cast(interner) + .intern(interner); + + let eq_goal: Goal = EqGoal { + a: new_tuple.cast(interner), + b: target_ty.clone().cast(interner), + } + .cast(interner); + + // Check that `T: Unsize` + let last_field_unsizing_goal: Goal = TraitRef { + trait_id: unsize_trait_id, + substitution: Substitution::from(interner, [tail_ty_a, tail_ty_b].iter().cloned()), + } + .cast(interner); + + builder.push_clause( + trait_ref.clone(), + [eq_goal, last_field_unsizing_goal].iter(), + ); + } + + _ => (), + } +} diff --git a/chalk-solve/src/rust_ir.rs b/chalk-solve/src/rust_ir.rs index 63865a6f499..822bae44be2 100644 --- a/chalk-solve/src/rust_ir.rs +++ b/chalk-solve/src/rust_ir.rs @@ -222,6 +222,7 @@ pub enum WellKnownTrait { FnOnce, FnMut, Fn, + Unsize, } impl TraitDatum { diff --git a/chalk-solve/src/wf.rs b/chalk-solve/src/wf.rs index a28b82afb84..b963eebde72 100644 --- a/chalk-solve/src/wf.rs +++ b/chalk-solve/src/wf.rs @@ -476,7 +476,8 @@ impl WfWellKnownGoals { | WellKnownTrait::Sized | WellKnownTrait::FnOnce | WellKnownTrait::FnMut - | WellKnownTrait::Fn => None, + | WellKnownTrait::Fn + | WellKnownTrait::Unsize => None, } } @@ -490,13 +491,14 @@ impl WfWellKnownGoals { let interner = db.interner(); match db.trait_datum(impl_datum.trait_id()).well_known? { - // You can't add a manual implementation of Sized + WellKnownTrait::Drop => Self::drop_impl_constraint(db, impl_datum), + WellKnownTrait::Copy | WellKnownTrait::Clone => None, + // You can't add a manual implementation for following traits: WellKnownTrait::Sized | WellKnownTrait::FnOnce | WellKnownTrait::FnMut - | WellKnownTrait::Fn => Some(GoalData::CannotProve(()).intern(interner)), - WellKnownTrait::Drop => Self::drop_impl_constraint(db, impl_datum), - WellKnownTrait::Copy | WellKnownTrait::Clone => None, + | WellKnownTrait::Fn + | WellKnownTrait::Unsize => Some(GoalData::CannotProve(()).intern(interner)), } } diff --git a/tests/test/auto_traits.rs b/tests/test/auto_traits.rs index 556983d1bd4..25b6f844947 100644 --- a/tests/test/auto_traits.rs +++ b/tests/test/auto_traits.rs @@ -6,7 +6,7 @@ use super::*; fn auto_semantics() { test! { program { - #[lang(sized)] trait Sized { } + trait Sized { } #[auto] trait Send { } struct TypeA { } @@ -14,7 +14,7 @@ fn auto_semantics() { struct Ptr { } impl Send for Ptr where T: Send { } - struct List where T: Sized { + struct List { data: T, next: Ptr> } diff --git a/tests/test/builtin_impls.rs b/tests/test/builtin_impls.rs new file mode 100644 index 00000000000..f3884946dee --- /dev/null +++ b/tests/test/builtin_impls.rs @@ -0,0 +1,5 @@ +mod clone; +mod copy; +mod fn_family; +mod sized; +mod unsize; diff --git a/tests/test/builtin_impls/clone.rs b/tests/test/builtin_impls/clone.rs new file mode 100644 index 00000000000..1ab37f581f3 --- /dev/null +++ b/tests/test/builtin_impls/clone.rs @@ -0,0 +1,64 @@ +use crate::test::*; + +#[test] +fn tuples_are_clone() { + test! { + program { + #[non_enumerable] // see above + #[lang(clone)] + trait Clone { } + + struct S {} + + impl Clone for u8 {} + } + + goal { + ([u8],): Clone + } yields { + "No possible solution" + } + + goal { + (u8, [u8]): Clone + } yields { + "No possible solution" + } + + goal { + ([u8], u8): Clone + } yields { + "No possible solution" + } + + goal { + (): Clone + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8,): Clone + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8, u8): Clone + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + exists { (T, u8): Clone } + } yields { + "Ambiguous" + } + + goal { + forall { if (T: Clone) { (T, u8): Clone } } + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} diff --git a/tests/test/builtin_impls/copy.rs b/tests/test/builtin_impls/copy.rs new file mode 100644 index 00000000000..542b29beef2 --- /dev/null +++ b/tests/test/builtin_impls/copy.rs @@ -0,0 +1,67 @@ +use crate::test::*; + +#[test] +fn tuples_are_copy() { + test! { + program { + // FIXME: If we don't declare Copy non-enumerable, `exists { T: + // Copy }` gives wrong results, because it doesn't consider the + // built-in impls. + #[non_enumerable] + #[lang(copy)] + trait Copy { } + + struct S {} + + impl Copy for u8 {} + } + + goal { + ([u8],): Copy + } yields { + "No possible solution" + } + + goal { + (u8, [u8]): Copy + } yields { + "No possible solution" + } + + goal { + ([u8], u8): Copy + } yields { + "No possible solution" + } + + goal { + (): Copy + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8,): Copy + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8, u8): Copy + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + exists { (T, u8): Copy } + } yields { + "Ambiguous" + } + + goal { + forall { if (T: Copy) { (T, u8): Copy } } + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} diff --git a/tests/test/builtin_impls/fn_family.rs b/tests/test/builtin_impls/fn_family.rs new file mode 100644 index 00000000000..c88dddca91c --- /dev/null +++ b/tests/test/builtin_impls/fn_family.rs @@ -0,0 +1,182 @@ +use crate::test::*; + +#[test] +fn function_implement_fn_traits() { + test! { + program { + #[lang(fn_once)] + trait FnOnce { + type Output; + } + + #[lang(fn_mut)] + trait FnMut where Self: FnOnce { } + + #[lang(fn)] + trait Fn where Self: FnMut { } + + struct Ty { } + + trait Clone { } + opaque type MyOpaque: Clone = Ty; + + } + + // Simple test: make sure a fully monomorphic type implements FnOnce + goal { + fn(u8): FnOnce<(u8,)> + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Same as above, but for FnMut + goal { + fn(u8): FnMut<(u8,)> + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Same as above, but for Fn + goal { + fn(u8): Fn<(u8,)> + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Function pointres implicity return `()` when no return + // type is specified - make sure that normalization understands + // this + goal { + Normalize(>::Output -> ()) + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Tests normalizing when an explicit return type is used + goal { + Normalize( bool as FnOnce<(u8,)>>::Output -> bool) + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Tests that we fail to normalize when there's a mismatch with + // fully monomorphic types. + goal { + Normalize( bool as FnOnce<(u8,)>>::Output -> u8) + } yields { + "No possible solution" + } + + // Ensures that we don't find a solution when doing so would + // require us to conclude that two different universally quantified + // types (T and V) are equal. + goal { + forall { + Normalize( T as FnOnce<(u8, V)>>::Output -> V) + } + } yields { + "No possible solution" + } + + // Tests that we can normalize a generic function pointer type + goal { + forall { + exists { + Normalize( T as FnOnce<(u8, V)>>::Output -> U) + } + } + } yields { + "Unique; substitution [?0 := !1_0], lifetime constraints []" + } + + // Tests that we properly tuple function arguments when constrcting + // the `FnOnce` impl + goal { + fn(u8, u32): FnOnce<(u8,u32)> + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Tests that we don't find a solution when fully monomorphic + // types are mismatched + goal { + fn(i32): FnOnce<(bool,)> + } yields { + "No possible solution" + } + + // Tests function pointer types that use the function's binder + // Universally quantified lifetimes that differ only in their + // name ('a vs 'b) should be considered equivalent here + goal { + forall<'a> { + for<'b> fn(&'b u8): FnOnce<(&'a u8,)> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Tests that a 'stricter' function (requires lifetimes to be the same) + // can implement `FnOnce` for a 'less strict' signature (dose not require + // lifetimes to be the same), provided that the lifetimes are *actually* + // the same. + goal { + forall<'a, 'b> { + for<'c> fn(&'c u8, &'c i32): FnOnce<(&'a u8, &'b i32)> + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_1 }, InEnvironment { environment: Env([]), goal: '!1_1: '!1_0 }]" + } + + // Tests the opposite case as the previous test: a 'less strict' function + // (does not require lifetimes to be the same) can implement `FnOnce` for + // a 'stricter' signature (requires lifetimes to be the same) without + // any additional requirements + goal { + forall<'a> { + for<'b, 'c> fn(&'b u8, &'c i32): FnOnce<(&'a u8, &'a i32)> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Similiar to the above test, but for types instead of lifetimes: + // a 'stricter' function (requires types to be the same) can never + // implement `FnOnce` for a 'less strict' signature (does not require + // types to be the same) + goal { + forall { + fn(T, T): FnOnce<(T, U)> + } + } yields { + "No possible solution" + } + + // Tests the opposite case as a previous test: a 'less strict' + // function can never implement 'FnOnce' for a 'more strict' signature + // (does not require types to bthe same) + goal { + forall { + fn(T, U): FnOnce<(T, T)> + } + } yields { + "No possible solution" + } + + // Tests that we flounder for inference variables + goal { + exists { + T: FnOnce<()> + } + } yields_first[SolverChoice::slg(3, None)] { + "Floundered" + } + + // Tests that we flounder for alias type (opaque) + goal { + MyOpaque: FnOnce<()> + } yields_first[SolverChoice::slg(3, None)] { + "Floundered" + } + } +} diff --git a/tests/test/builtin_impls/sized.rs b/tests/test/builtin_impls/sized.rs new file mode 100644 index 00000000000..4132e2689d5 --- /dev/null +++ b/tests/test/builtin_impls/sized.rs @@ -0,0 +1,125 @@ +use crate::test::*; + +#[test] +fn tuples_are_sized() { + test! { + program { + #[lang(sized)] + trait Sized { } + + trait Foo {} + } + + goal { + ([u8],): Sized + } yields { + "No possible solution" + } + + goal { + (u8, [u8]): Sized + } yields { + "No possible solution" + } + + // It should not be well-formed because for tuples, only + // the last element is allowed not to be Sized. + goal { + ([u8], u8): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8,): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + (u8, u8): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + exists { (T, u8): Sized } + } yields { + "Unique; for { substitution [?0 := ^0.0], lifetime constraints [] }" + } + + goal { + forall { (T, u8): Sized } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + forall { (u8, T): Sized } + } yields { + "No possible solution" + } + + goal { + forall { if (T: Sized) { (u8, T): Sized } } + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} + +#[test] +fn functions_are_sized() { + test! { + program { + #[lang(sized)] + trait Sized { } + + trait Copy {} + } + + goal { + fn(()): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + fn([u8]): Sized + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} + +#[test] +fn scalars_are_sized() { + test! { + program { + #[lang(sized)] trait Sized { } + } + + goal { i8: Sized } yields { "Unique" } + goal { i16: Sized } yields { "Unique" } + goal { i32: Sized } yields { "Unique" } + goal { i64: Sized } yields { "Unique" } + goal { i128: Sized } yields { "Unique" } + goal { isize: Sized } yields { "Unique" } + goal { u8: Sized } yields { "Unique" } + goal { u16: Sized } yields { "Unique" } + goal { u32: Sized } yields { "Unique" } + goal { u64: Sized } yields { "Unique" } + goal { u128: Sized } yields { "Unique" } + goal { usize: Sized } yields { "Unique" } + goal { f32: Sized } yields { "Unique" } + goal { f64: Sized } yields { "Unique" } + goal { bool: Sized } yields { "Unique" } + goal { char: Sized } yields { "Unique" } + } +} diff --git a/tests/test/builtin_impls/unsize.rs b/tests/test/builtin_impls/unsize.rs new file mode 100644 index 00000000000..b3fab6c007b --- /dev/null +++ b/tests/test/builtin_impls/unsize.rs @@ -0,0 +1,592 @@ +use crate::test::*; + +#[test] +fn dyn_to_dyn_unsizing() { + test! { + program { + #[lang(unsize)] + trait Unsize {} + + #[object_safe] + trait Principal {} + #[object_safe] + trait OtherPrincipal {} + #[object_safe] + trait GenericPrincipal { + type Item; + } + + #[auto] + #[object_safe] + trait Auto1 {} + + #[auto] + #[object_safe] + trait Auto2 {} + + #[auto] + #[object_safe] + trait Auto3 {} + } + + // Tests with the same principal and auto traits + goal { + forall<'a> { + forall<'b> { + dyn Principal + 'a: Unsize + } + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!2_0 }]" + } + + goal { + forall<'a> { + forall<'b> { + dyn Principal + Auto1 + Auto2 + Auto3 + 'a: Unsize + } + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!2_0 }]" + } + + // Target has a subset of source auto traits + goal { + forall<'a> { + dyn Principal + Auto1 + Auto2 + 'a: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]" + } + + // Both target and source don't have principal as their first trait + goal { + forall<'a> { + dyn Auto1 + Principal + 'a: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]" + } + + // Different order of traits in target and source + // FIXME: this doesn't work because trait object unification + // respects where clause order, which it shouldn't + goal { + forall<'a> { + dyn Principal + Auto1 + 'a: Unsize + } + } yields { + "No possible solution" + } + + // See above + goal { + forall<'a> { + dyn Principal + Auto2 + Auto1 + 'a: Unsize + } + } yields { + "No possible solution" + } + + // Source has a subset of auto traits of target + goal { + forall<'a> { + dyn Principal + Auto2 + 'a: Unsize + } + } yields { + "No possible solution" + } + + // Source and target have different set of auto traits + goal { + forall<'a> { + dyn Principal + Auto1 + Auto2 + 'a: Unsize + } + } yields { + "No possible solution" + } + + // Source has a principal trait, while target doesnt, both have the same auto traits. + goal { + forall<'a> { + dyn Principal + Auto1 + 'a: Unsize + } + } yields { + "No possible solution" + } + + // Non-matching principal traits + goal { + forall<'a> { + dyn Principal + 'a: Unsize + } + } yields { + "No possible solution" + } + + // Matching generic principal traits + goal { + forall<'a> { + dyn GenericPrincipal + 'a: Unsize + 'a> + } + } yields { + "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_0 }]" + } + + // Non-matching generic principal traits + goal { + forall<'a> { + dyn GenericPrincipal + 'a: Unsize + 'a> + } + } yields { + "No possible solution" + } + } +} + +#[test] +fn ty_to_dyn_unsizing() { + test! { + program { + #[lang(unsize)] + trait Unsize {} + #[lang(sized)] + trait Sized {} + + #[object_safe] + trait Principal {} + #[object_safe] + trait GenericPrincipal { + type Item; + } + + trait UnsafePrincipal {} + + #[auto] + #[object_safe] + trait Auto {} + + struct Foo {} + struct FooLifetime<'a> {} + struct Bar {} + struct Baz {} + struct FooNotSized { + t: T + } + + impl Principal for Foo {} + impl UnsafePrincipal for Foo {} + + impl<'a> Principal for FooLifetime<'a> {} + + impl Principal for Bar {} + impl !Auto for Bar {} + + impl Principal for FooNotSized {} + + impl GenericPrincipal for Foo { + type Item = u32; + } + } + + goal { + forall<'a> { + Foo: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Principal is not the first trait + goal { + forall<'a> { + Foo: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Auto-only trait object + goal { + forall<'a> { + Foo: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // TypeOutlives test + // FIXME: this should create a constraint 'a: 'a, but currently + // we have no `TypeOutlives` goal to produce it + goal { + forall<'a> { + FooLifetime<'a>: Unsize + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // See above + goal { + forall<'a> { + exists<'b> { + FooLifetime<'a>: Unsize + } + } + } yields { + "Unique; for { substitution [?0 := '^0.0], lifetime constraints [] }" + } + + // Source does not implement auto trait (with principal) + goal { + forall<'a> { + Bar: Unsize + } + } yields { + "No possible solution" + } + + // Source does not implement auto trait (without principal) + goal { + forall<'a> { + Bar: Unsize + } + } yields { + "No possible solution" + } + + // Source does not implement principal + goal { + forall<'a> { + Baz: Unsize + } + } yields { + "No possible solution" + } + + // Implemeted generic principal + goal { + forall<'a> { + Foo: Unsize + 'a> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + + // Non-implemeted generic principal + goal { + forall<'a> { + Foo: Unsize + 'a> + } + } yields { + "No possible solution" + } + + // Not object-safe principal trait + goal { + forall<'a> { + Foo: Unsize + } + } yields { + "No possible solution" + } + + // Source ty is not Sized + goal { + forall<'a> { + forall { + FooNotSized: Unsize + } + } + } yields { + "No possible solution" + } + + // Sized counterpart for the previous test + goal { + forall<'a> { + forall { + if (T: Sized) { + FooNotSized: Unsize + } + } + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} + +#[test] +fn tuple_unsizing() { + test! { + program { + #[lang(unsize)] + trait Unsize {} + #[lang(sized)] + trait Sized {} + + struct Foo {} + + #[object_safe] + trait Principal {} + #[object_safe] + trait OtherPrincipal {} + + impl Principal for Foo {} + } + + goal { + (): Unsize<()> + } yields { + "No possible solution" + } + + goal { + (u32, u32): Unsize<(u32, u32)> + } yields { + "No possible solution" + } + + goal { + forall<'a> { + (u32, Foo): Unsize<(u32, dyn Principal + 'a)> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Last field does not implement `Unsize` + goal { + forall<'a> { + (u32, Foo): Unsize<(u32, dyn OtherPrincipal + 'a)> + } + } yields { + "No possible solution" + } + + // Mismatch of head fields + goal { + forall<'a> { + (u32, Foo): Unsize<(u64, dyn Principal + 'a)> + } + } yields { + "No possible solution" + } + + // Tuple length mismatch + goal { + forall<'a> { + (u32, u32, Foo): Unsize<(u32, dyn Principal + 'a)> + } + } yields { + "No possible solution" + } + + // Multilevel tuple test + goal { + forall<'a> { + (u32, (u32, Foo)): Unsize<(u32, (u32, dyn Principal + 'a))> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + } +} + +#[test] +fn array_unsizing() { + test! { + program { + #[lang(unsize)] + trait Unsize {} + + struct Foo {} + } + + goal { + [Foo; 2]: Unsize<[Foo]> + } yields { + "Unique" + } + + goal { + [Foo; 5]: Unsize<[Foo]> + } yields { + "No possible solution" + } + } +} + +#[test] +fn struct_unsizing() { + test! { + program { + #[lang(unsize)] + trait Unsize {} + #[lang(sized)] + trait Sized {} + + struct Foo {} + struct Bar {} + struct Baz<'a> {} + + struct S1 { + t1: T1 + } + + struct S12 where T1: Sized { + t1: T1, + t2: T2 + } + + struct SParamsInMultipleFields { + t1: Bar, + t2: T + } + + struct SNested where T1: Sized, T2: Sized { + t1: T1, + t2: (T2, S1) + } + + struct SBad where T1: Sized { + t1: Bar>, + t2: (T1, S1) + } + + struct SWithBinders where T1: Sized { + t1: T1, + t2: for<'a> fn(dyn Principal + 'a), + t3: T3 + } + + struct SLifetime<'a, T> { + t1: Baz<'a>, + t2: S12, T> + } + + struct SConst { + t: T + } + + struct SGoodConst { + t1: u32, + t2: SConst + } + + struct SBadConst { + t1: [u32; N], + t2: SConst + } + + #[object_safe] + trait Principal {} + #[object_safe] + trait OtherPrincipal {} + + impl Principal for Foo {} + } + + // Single field struct tests + goal { + Foo: Unsize + } yields { + "No possible solution" + } + + goal { + forall<'a> { + S1: Unsize> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + forall<'a> { + S1: Unsize> + } + } yields { + "No possible solution" + } + + // Unsizing parameter is used in head fields + goal { + forall<'a> { + SParamsInMultipleFields: + Unsize> + } + } yields { + "No possible solution" + } + + // Two-field struct test + goal { + forall<'a> { + S12: Unsize> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Test for the unsizing parameters collector + // (checking that it ignores the binder inside `SWithBinders`) + goal { + forall<'a> { + SWithBinders: Unsize> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Non-trivial unsizing of the last field + goal { + forall<'a> { + SNested, Foo>: Unsize, dyn Principal + 'a>> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + goal { + forall<'a> { + SBad: Unsize> + } + } yields { + "No possible solution" + } + + // Check that lifetimes can't be used as unsizing parameters + goal { + forall<'a> { + SLifetime<'a, Foo>: Unsize> + } + } yields { + "Unique; substitution [], lifetime constraints []" + } + + // Tests with constant as an unsizing parameter + goal { + SGoodConst<5, [u32; 2]>: Unsize> + } yields { + "Unique; substitution [], lifetime constraints []" + } + + + // Target does not match source + goal { + SGoodConst<4, [u32; 2]>: Unsize> + } yields { + "No possible solution" + } + + // Unsizing parameter is used in head fields + goal { + SBadConst<5, [u32; 2]>: Unsize> + } yields { + "No possible solution" + } + } +} diff --git a/tests/test/functions.rs b/tests/test/functions.rs index 058e35d80ac..48a4efa8ae5 100644 --- a/tests/test/functions.rs +++ b/tests/test/functions.rs @@ -1,210 +1,5 @@ use super::*; -#[test] -fn function_implement_fn_traits() { - test! { - program { - #[lang(fn_once)] - trait FnOnce { - type Output; - } - - #[lang(fn_mut)] - trait FnMut where Self: FnOnce { } - - #[lang(fn)] - trait Fn where Self: FnMut { } - - struct Ty { } - - trait Clone { } - opaque type MyOpaque: Clone = Ty; - - } - - // Simple test: make sure a fully monomorphic type implements FnOnce - goal { - fn(u8): FnOnce<(u8,)> - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Same as above, but for FnMut - goal { - fn(u8): FnMut<(u8,)> - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Same as above, but for Fn - goal { - fn(u8): Fn<(u8,)> - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Function pointres implicity return `()` when no return - // type is specified - make sure that normalization understands - // this - goal { - Normalize(>::Output -> ()) - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Tests normalizing when an explicit return type is used - goal { - Normalize( bool as FnOnce<(u8,)>>::Output -> bool) - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Tests that we fail to normalize when there's a mismatch with - // fully monomorphic types. - goal { - Normalize( bool as FnOnce<(u8,)>>::Output -> u8) - } yields { - "No possible solution" - } - - // Ensures that we don't find a solution when doing so would - // require us to conclude that two different universally quantified - // types (T and V) are equal. - goal { - forall { - Normalize( T as FnOnce<(u8, V)>>::Output -> V) - } - } yields { - "No possible solution" - } - - // Tests that we can normalize a generic function pointer type - goal { - forall { - exists { - Normalize( T as FnOnce<(u8, V)>>::Output -> U) - } - } - } yields { - "Unique; substitution [?0 := !1_0], lifetime constraints []" - } - - // Tests that we properly tuple function arguments when constrcting - // the `FnOnce` impl - goal { - fn(u8, u32): FnOnce<(u8,u32)> - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Tests that we don't find a solution when fully monomorphic - // types are mismatched - goal { - fn(i32): FnOnce<(bool,)> - } yields { - "No possible solution" - } - - // Tests function pointer types that use the function's binder - // Universally quantified lifetimes that differ only in their - // name ('a vs 'b) should be considered equivalent here - goal { - forall<'a> { - for<'b> fn(&'b u8): FnOnce<(&'a u8,)> - } - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Tests that a 'stricter' function (requires lifetimes to be the same) - // can implement `FnOnce` for a 'less strict' signature (dose not require - // lifetimes to be the same), provided that the lifetimes are *actually* - // the same. - goal { - forall<'a, 'b> { - for<'c> fn(&'c u8, &'c i32): FnOnce<(&'a u8, &'b i32)> - } - } yields { - "Unique; substitution [], lifetime constraints [InEnvironment { environment: Env([]), goal: '!1_0: '!1_1 }, InEnvironment { environment: Env([]), goal: '!1_1: '!1_0 }]" - } - - // Tests the opposite case as the previous test: a 'less strict' function - // (does not require lifetimes to be the same) can implement `FnOnce` for - // a 'stricter' signature (requires lifetimes to be the same) without - // any additional requirements - goal { - forall<'a> { - for<'b, 'c> fn(&'b u8, &'c i32): FnOnce<(&'a u8, &'a i32)> - } - } yields { - "Unique; substitution [], lifetime constraints []" - } - - // Similiar to the above test, but for types instead of lifetimes: - // a 'stricter' function (requires types to be the same) can never - // implement `FnOnce` for a 'less strict' signature (does not require - // types to be the same) - goal { - forall { - fn(T, T): FnOnce<(T, U)> - } - } yields { - "No possible solution" - } - - // Tests the opposite case as a previous test: a 'less strict' - // function can never implement 'FnOnce' for a 'more strict' signature - // (does not require types to bthe same) - goal { - forall { - fn(T, U): FnOnce<(T, T)> - } - } yields { - "No possible solution" - } - - // Tests that we flounder for inference variables - goal { - exists { - T: FnOnce<()> - } - } yields_first[SolverChoice::slg(3, None)] { - "Floundered" - } - - // Tests that we flounder for alias type (opaque) - goal { - MyOpaque: FnOnce<()> - } yields_first[SolverChoice::slg(3, None)] { - "Floundered" - } - } -} - -#[test] -fn functions_are_sized() { - test! { - program { - #[lang(sized)] - trait Sized { } - - trait Copy {} - } - - goal { - fn(()): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - fn([u8]): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - } -} - #[test] fn fn_defs() { test! { diff --git a/tests/test/mod.rs b/tests/test/mod.rs index ae298dd6be9..d91afc0201c 100644 --- a/tests/test/mod.rs +++ b/tests/test/mod.rs @@ -298,13 +298,13 @@ fn solve_goal(program_text: &str, goals: Vec<(&str, SolverChoice, TestGoal)>) { mod arrays; mod auto_traits; +mod builtin_impls; mod coherence_goals; mod coinduction; mod constants; mod cycle; mod existential_types; mod fn_def; -mod functions; mod implied_bounds; mod impls; mod misc; diff --git a/tests/test/scalars.rs b/tests/test/scalars.rs index 210516ad855..d612e135f64 100644 --- a/tests/test/scalars.rs +++ b/tests/test/scalars.rs @@ -136,29 +136,3 @@ fn scalars_are_well_formed() { goal { WellFormed(char) } yields { "Unique" } } } - -#[test] -fn scalars_are_sized() { - test! { - program { - #[lang(sized)] trait Sized { } - } - - goal { i8: Sized } yields { "Unique" } - goal { i16: Sized } yields { "Unique" } - goal { i32: Sized } yields { "Unique" } - goal { i64: Sized } yields { "Unique" } - goal { i128: Sized } yields { "Unique" } - goal { isize: Sized } yields { "Unique" } - goal { u8: Sized } yields { "Unique" } - goal { u16: Sized } yields { "Unique" } - goal { u32: Sized } yields { "Unique" } - goal { u64: Sized } yields { "Unique" } - goal { u128: Sized } yields { "Unique" } - goal { usize: Sized } yields { "Unique" } - goal { f32: Sized } yields { "Unique" } - goal { f64: Sized } yields { "Unique" } - goal { bool: Sized } yields { "Unique" } - goal { char: Sized } yields { "Unique" } - } -} diff --git a/tests/test/tuples.rs b/tests/test/tuples.rs index 41c3cf8bf78..5542f8ae2d3 100644 --- a/tests/test/tuples.rs +++ b/tests/test/tuples.rs @@ -34,206 +34,3 @@ fn tuple_trait_impl() { } } } - -#[test] -fn tuples_are_sized() { - test! { - program { - #[lang(sized)] - trait Sized { } - - trait Foo {} - } - - goal { - ([u8],): Sized - } yields { - "No possible solution" - } - - goal { - (u8, [u8]): Sized - } yields { - "No possible solution" - } - - // It should not be well-formed because for tuples, only - // the last element is allowed not to be Sized. - goal { - ([u8], u8): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8,): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8, u8): Sized - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - exists { (T, u8): Sized } - } yields { - "Unique; for { substitution [?0 := ^0.0], lifetime constraints [] }" - } - - goal { - forall { (T, u8): Sized } - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - forall { (u8, T): Sized } - } yields { - "No possible solution" - } - - goal { - forall { if (T: Sized) { (u8, T): Sized } } - } yields { - "Unique; substitution [], lifetime constraints []" - } - } -} - -#[test] -fn tuples_are_copy() { - test! { - program { - // FIXME: If we don't declare Copy non-enumerable, `exists { T: - // Copy }` gives wrong results, because it doesn't consider the - // built-in impls. - #[non_enumerable] - #[lang(copy)] - trait Copy { } - - trait Foo {} - - impl Copy for u8 {} - } - - goal { - ([u8],): Copy - } yields { - "No possible solution" - } - - goal { - (u8, [u8]): Copy - } yields { - "No possible solution" - } - - goal { - ([u8], u8): Copy - } yields { - "No possible solution" - } - - goal { - (): Copy - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8,): Copy - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8, u8): Copy - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - exists { (T, u8): Copy } - } yields { - "Ambiguous" - } - - goal { - forall { if (T: Copy) { (T, u8): Copy } } - } yields { - "Unique; substitution [], lifetime constraints []" - } - } -} - -#[test] -fn tuples_are_clone() { - test! { - program { - #[non_enumerable] // see above - #[lang(clone)] - trait Clone { } - - trait Foo {} - - impl Clone for u8 {} - } - - goal { - ([u8],): Clone - } yields { - "No possible solution" - } - - goal { - (u8, [u8]): Clone - } yields { - "No possible solution" - } - - goal { - ([u8], u8): Clone - } yields { - "No possible solution" - } - - goal { - (): Clone - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8,): Clone - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - (u8, u8): Clone - } yields { - "Unique; substitution [], lifetime constraints []" - } - - goal { - exists { (T, u8): Clone } - } yields { - "Ambiguous" - } - - goal { - forall { if (T: Clone) { (T, u8): Clone } } - } yields { - "Unique; substitution [], lifetime constraints []" - } - } -} diff --git a/tests/test/wf_lowering.rs b/tests/test/wf_lowering.rs index e2dd6e3d1b5..ab63b7a8700 100644 --- a/tests/test/wf_lowering.rs +++ b/tests/test/wf_lowering.rs @@ -819,3 +819,17 @@ fn drop_constraints() { } } } + +#[test] +fn no_unsize_impls() { + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + impl Unsize for u32 {} + } error_msg { + "trait impl for `Unsize` does not meet well-formedness requirements" + } + } +}