diff --git a/book/src/clauses/well_known_traits.md b/book/src/clauses/well_known_traits.md index b5cdb76d154..72b4fb1ed3b 100644 --- a/book/src/clauses/well_known_traits.md +++ b/book/src/clauses/well_known_traits.md @@ -28,26 +28,26 @@ Some common examples of auto traits are `Send` and `Sync`. [coinductive_section]: ../engine/logic/coinduction.html#coinduction-and-refinement-strands # Current state -| Type | Copy | Clone | Sized | Unsize | Drop | FnOnce/FnMut/Fn | Unpin | Generator | auto traits | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| tuple types | ✅ | ✅ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| structs | ⚬ | ⚬ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| scalar types | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| str | 📚 | 📚 | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| never type | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| trait objects | ⚬ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | -| functions defs | ✅ | ✅ | ✅ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ✅ | -| functions ptrs | ✅ | ✅ | ✅ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ✅ | -| raw ptrs | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| immutable refs | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| mutable refs | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| slices | ⚬ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| arrays | ✅ | ✅ | ✅ | ❌ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | -| closures❌ | ❌ | ❌ | ❌ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ✅ | -| generators❌ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ⚬ | ❌ | ❌ | ❌ | -| gen. witness❌ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ❌ | -| ----------- | | | | | | | | | | -| well-formedness | ✅ | ⚬ | ✅ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | +| Type | Copy | Clone | Sized | Unsize | CoerceUnsized | Drop | FnOnce/FnMut/Fn | Unpin | Generator | auto traits | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| tuple types | ✅ | ✅ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| structs | ⚬ | ⚬ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| scalar types | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| str | 📚 | 📚 | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| never type | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| trait objects | ⚬ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | +| functions defs | ✅ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ✅ | +| functions ptrs | ✅ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ✅ | +| raw ptrs | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| immutable refs | 📚 | 📚 | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| mutable refs | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| slices | ⚬ | ⚬ | ⚬ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| arrays | ✅ | ✅ | ✅ | ❌ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ✅ | +| closures❌ | ❌ | ❌ | ❌ | ⚬ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ✅ | +| generators❌ | ⚬ | ⚬ | ❌ | ⚬ | ⚬ | ⚬ | ⚬ | ❌ | ❌ | ❌ | +| gen. witness❌ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ⚬ | ❌ | +| ----------- | | | | | | | | | | | +| well-formedness | ✅ | ⚬ | ✅ | ⚬ | ✅ | ✅ | ⚬ | ⚬ | ⚬ | ⚬ | legend: ⚬ - not applicable diff --git a/chalk-integration/src/lowering.rs b/chalk-integration/src/lowering.rs index 3d540c25d1c..324c25f68f9 100644 --- a/chalk-integration/src/lowering.rs +++ b/chalk-integration/src/lowering.rs @@ -1156,6 +1156,7 @@ impl Lower for WellKnownTrait { WellKnownTrait::Fn => rust_ir::WellKnownTrait::Fn, WellKnownTrait::Unsize => rust_ir::WellKnownTrait::Unsize, WellKnownTrait::Unpin => rust_ir::WellKnownTrait::Unpin, + WellKnownTrait::CoerceUnsized => rust_ir::WellKnownTrait::CoerceUnsized, } } } diff --git a/chalk-ir/src/interner.rs b/chalk-ir/src/interner.rs index 4accb4ac46a..b4da73c53d6 100644 --- a/chalk-ir/src/interner.rs +++ b/chalk-ir/src/interner.rs @@ -726,6 +726,16 @@ where type Interner = I; } +impl HasInterner for (A, B, C) +where + A: HasInterner, + B: HasInterner, + C: HasInterner, + I: Interner, +{ + type Interner = I; +} + impl<'a, T: HasInterner> HasInterner for std::slice::Iter<'a, T> { type Interner = T::Interner; } diff --git a/chalk-ir/src/lib.rs b/chalk-ir/src/lib.rs index 1963cd2421a..2464b1dc248 100644 --- a/chalk-ir/src/lib.rs +++ b/chalk-ir/src/lib.rs @@ -477,6 +477,17 @@ impl Ty { } } + /// Returns `Some(adt_id)` if this is an ADT, `None` otherwise + pub fn adt_id(&self, interner: &I) -> Option> { + match self.data(interner) { + TyData::Apply(ApplicationTy { + name: TypeName::Adt(adt_id), + .. + }) => Some(*adt_id), + _ => None, + } + } + /// True if this type contains "bound" types/lifetimes, and hence /// needs to be shifted across binders. This is a very inefficient /// check, intended only for debug assertions, because I am lazy. diff --git a/chalk-parse/src/ast.rs b/chalk-parse/src/ast.rs index 7fb0686cbb1..f80b8282af1 100644 --- a/chalk-parse/src/ast.rs +++ b/chalk-parse/src/ast.rs @@ -127,6 +127,7 @@ pub enum WellKnownTrait { Fn, Unsize, Unpin, + CoerceUnsized, } #[derive(Clone, PartialEq, Eq, Debug)] diff --git a/chalk-parse/src/parser.lalrpop b/chalk-parse/src/parser.lalrpop index 02ed68b2a7a..7947774c75c 100644 --- a/chalk-parse/src/parser.lalrpop +++ b/chalk-parse/src/parser.lalrpop @@ -63,6 +63,7 @@ WellKnownTrait: WellKnownTrait = { "#" "[" "lang" "(" "fn" ")" "]" => WellKnownTrait::Fn, "#" "[" "lang" "(" "unsize" ")" "]" => WellKnownTrait::Unsize, "#" "[" "lang" "(" "unpin" ")" "]" => WellKnownTrait::Unpin, + "#" "[" "lang" "(" "coerce_unsized" ")" "]" => WellKnownTrait::CoerceUnsized, }; AdtRepr: Atom = "#" "[" "repr" "(" ")" "]" => name.str; diff --git a/chalk-solve/src/clauses/builtin_traits.rs b/chalk-solve/src/clauses/builtin_traits.rs index 9a50e5f29f8..6a4eed37592 100644 --- a/chalk-solve/src/clauses/builtin_traits.rs +++ b/chalk-solve/src/clauses/builtin_traits.rs @@ -41,12 +41,8 @@ pub fn add_builtin_program_clauses( WellKnownTrait::Unsize => { unsize::add_unsize_program_clauses(db, builder, &trait_ref, ty) } - - // Drop impls are provided explicitly - WellKnownTrait::Drop => (), - - // There are no special rules for Unpin - WellKnownTrait::Unpin => (), + // There are no builtin impls provided for the following traits: + WellKnownTrait::Unpin | WellKnownTrait::Drop | WellKnownTrait::CoerceUnsized => (), } Ok(()) }) diff --git a/chalk-solve/src/coherence/solve.rs b/chalk-solve/src/coherence/solve.rs index 98081ce9b87..25fb25344d4 100644 --- a/chalk-solve/src/coherence/solve.rs +++ b/chalk-solve/src/coherence/solve.rs @@ -251,10 +251,7 @@ impl CoherenceSolver<'_, I> { let canonical_goal = &goal.into_closed_goal(interner); let mut fresh_solver = (self.solver_builder)(); - let result = match fresh_solver.solve(self.db, canonical_goal) { - Some(sol) => sol.is_unique(), - None => false, - }; + let result = fresh_solver.has_unique_solution(self.db, canonical_goal); debug!("specializes: result = {:?}", result); diff --git a/chalk-solve/src/display/items.rs b/chalk-solve/src/display/items.rs index 585c4eab467..62a2154d651 100644 --- a/chalk-solve/src/display/items.rs +++ b/chalk-solve/src/display/items.rs @@ -194,6 +194,7 @@ impl RenderAsRust for TraitDatum { WellKnownTrait::Fn => "fn", WellKnownTrait::Unsize => "unsize", WellKnownTrait::Unpin => "unpin", + WellKnownTrait::CoerceUnsized => "coerce_unsized", }; writeln!(f, "#[lang({})]", name)?; } diff --git a/chalk-solve/src/rust_ir.rs b/chalk-solve/src/rust_ir.rs index ec300d1721a..3dabc05f2fa 100644 --- a/chalk-solve/src/rust_ir.rs +++ b/chalk-solve/src/rust_ir.rs @@ -270,6 +270,7 @@ pub enum WellKnownTrait { Fn, Unsize, Unpin, + CoerceUnsized, } chalk_ir::const_visit!(WellKnownTrait); diff --git a/chalk-solve/src/solve.rs b/chalk-solve/src/solve.rs index dc74045acd6..8d0315219de 100644 --- a/chalk-solve/src/solve.rs +++ b/chalk-solve/src/solve.rs @@ -203,4 +203,17 @@ where goal: &UCanonical>>, f: &mut dyn FnMut(SubstitutionResult>>, bool) -> bool, ) -> bool; + + /// A convenience method for when one doesn't need the actual solution, + /// only whether or not one exists. + fn has_unique_solution( + &mut self, + program: &dyn RustIrDatabase, + goal: &UCanonical>>, + ) -> bool { + match self.solve(program, goal) { + Some(sol) => sol.is_unique(), + None => false, + } + } } diff --git a/chalk-solve/src/wf.rs b/chalk-solve/src/wf.rs index 80912736ed5..be09a9050fa 100644 --- a/chalk-solve/src/wf.rs +++ b/chalk-solve/src/wf.rs @@ -1,16 +1,15 @@ use std::{fmt, iter}; -use crate::ext::*; -use crate::goal_builder::GoalBuilder; -use crate::rust_ir::*; -use crate::solve::Solver; -use crate::split::Split; -use crate::RustIrDatabase; -use chalk_ir::cast::*; -use chalk_ir::fold::shift::Shift; -use chalk_ir::interner::Interner; -use chalk_ir::visit::{Visit, Visitor}; -use chalk_ir::*; +use crate::{ + ext::*, goal_builder::GoalBuilder, rust_ir::*, solve::Solver, split::Split, RustIrDatabase, +}; +use chalk_ir::{ + cast::*, + fold::shift::Shift, + interner::Interner, + visit::{Visit, Visitor}, + *, +}; use tracing::debug; #[derive(Debug)] @@ -199,7 +198,7 @@ where // each variant are sized. For `structs`, we relax this requirement to // all but the last field. let sized_constraint_goal = - WfWellKnownGoals::struct_sized_constraint( + WfWellKnownConstraints::struct_sized_constraint( gb.db(), fields, is_enum, @@ -226,10 +225,7 @@ where let wg_goal = wg_goal.into_closed_goal(interner); let mut fresh_solver = (self.solver_builder)(); - let is_legal = match fresh_solver.solve(self.db, &wg_goal) { - Some(sol) => sol.is_unique(), - None => false, - }; + let is_legal = fresh_solver.has_unique_solution(self.db, &wg_goal); if !is_legal { Err(WfError::IllFormedTypeDecl(adt_id)) @@ -254,13 +250,15 @@ where ), ); + if let Some(well_known) = self.db.trait_datum(trait_id).well_known { + self.verify_well_known_impl(impl_id, well_known)? + } + debug!("WF trait goal: {:?}", impl_goal); let mut fresh_solver = (self.solver_builder)(); - let is_legal = match fresh_solver.solve(self.db, &impl_goal.into_closed_goal(interner)) { - Some(sol) => sol.is_unique(), - None => false, - }; + let is_legal = + fresh_solver.has_unique_solution(self.db, &impl_goal.into_closed_goal(interner)); if is_legal { Ok(()) @@ -314,10 +312,7 @@ where debug!("WF opaque type goal: {:#?}", goal); let mut new_solver = (self.solver_builder)(); - let is_legal = match new_solver.solve(self.db, &goal.into_closed_goal(interner)) { - Some(sol) => sol.is_unique(), - None => false, - }; + let is_legal = new_solver.has_unique_solution(self.db, &goal.into_closed_goal(interner)); if is_legal { Ok(()) @@ -325,6 +320,45 @@ where Err(WfError::IllFormedOpaqueTypeDecl(opaque_ty_id)) } } + + /// Verify builtin rules for well-known traits + pub fn verify_well_known_impl( + &self, + impl_id: ImplId, + well_known: WellKnownTrait, + ) -> Result<(), WfError> { + let mut solver = (self.solver_builder)(); + let impl_datum = self.db.impl_datum(impl_id); + + let is_legal = match well_known { + WellKnownTrait::Copy => { + WfWellKnownConstraints::copy_impl_constraint(&mut *solver, self.db, &impl_datum) + } + WellKnownTrait::Drop => { + WfWellKnownConstraints::drop_impl_constraint(&mut *solver, self.db, &impl_datum) + } + WellKnownTrait::CoerceUnsized => { + WfWellKnownConstraints::coerce_unsized_impl_constraint( + &mut *solver, + self.db, + &impl_datum, + ) + } + WellKnownTrait::Clone | WellKnownTrait::Unpin => true, + // You can't add a manual implementation for the following traits: + WellKnownTrait::Fn + | WellKnownTrait::FnOnce + | WellKnownTrait::FnMut + | WellKnownTrait::Unsize + | WellKnownTrait::Sized => false, + }; + + if is_legal { + Ok(()) + } else { + Err(WfError::IllFormedTraitImpl(impl_datum.trait_id())) + } + } } fn impl_header_wf_goal( @@ -346,8 +380,6 @@ fn impl_header_wf_goal( let well_formed_goal = gb.forall(&impl_fields, (), |gb, _, (trait_ref, where_clauses), ()| { let interner = gb.interner(); - let trait_constraint_goal = WfWellKnownGoals::inside_impl(gb.db(), &trait_ref); - // if (WC && input types are well formed) { ... } gb.implies( impl_wf_environment(interner, &where_clauses, &trait_ref), @@ -368,20 +400,14 @@ fn impl_header_wf_goal( let goals = types .into_iter() .map(|ty| ty.well_formed().cast(interner)) - .chain(Some((*trait_ref).clone().well_formed().cast(interner))) - .chain(trait_constraint_goal.into_iter()); + .chain(Some((*trait_ref).clone().well_formed().cast(interner))); gb.all::<_, Goal>(goals) }, ) }); - Some( - gb.all( - iter::once(well_formed_goal) - .chain(WfWellKnownGoals::outside_impl(db, &impl_datum).into_iter()), - ), - ) + Some(well_formed_goal) } /// Creates the conditions that an impl (and its contents of an impl) @@ -545,49 +571,9 @@ fn compute_assoc_ty_goal( /// Defines methods to compute well-formedness goals for well-known /// traits (e.g. a goal for all fields of struct in a Copy impl to be Copy) -struct WfWellKnownGoals {} - -impl WfWellKnownGoals { - /// A convenience method to compute the goal assuming `trait_ref` - /// well-formedness requirements are in the environment. - pub fn inside_impl( - db: &dyn RustIrDatabase, - trait_ref: &TraitRef, - ) -> Option> { - match db.trait_datum(trait_ref.trait_id).well_known? { - WellKnownTrait::Copy => Self::copy_impl_constraint(db, trait_ref), - WellKnownTrait::Drop - | WellKnownTrait::Clone - | WellKnownTrait::Sized - | WellKnownTrait::FnOnce - | WellKnownTrait::FnMut - | WellKnownTrait::Fn - | WellKnownTrait::Unsize - | WellKnownTrait::Unpin => None, - } - } - - /// Computes well-formedness goals without any assumptions about the environment. - /// Note that `outside_impl` does not call `inside_impl`, one needs to call both - /// in order to get the full set of goals to be proven. - pub fn outside_impl( - db: &dyn RustIrDatabase, - impl_datum: &ImplDatum, - ) -> Option> { - let interner = db.interner(); - - match db.trait_datum(impl_datum.trait_id()).well_known? { - WellKnownTrait::Drop => Self::drop_impl_constraint(db, impl_datum), - WellKnownTrait::Copy | WellKnownTrait::Clone | WellKnownTrait::Unpin => None, - // You can't add a manual implementation for following traits: - WellKnownTrait::Sized - | WellKnownTrait::FnOnce - | WellKnownTrait::FnMut - | WellKnownTrait::Fn - | WellKnownTrait::Unsize => Some(GoalData::CannotProve.intern(interner)), - } - } +struct WfWellKnownConstraints; +impl WfWellKnownConstraints { /// Computes a goal to prove Sized constraints on a struct definition. /// Struct is considered well-formed (in terms of Sized) when it either /// has no fields or all of it's fields except the last are proven to be Sized. @@ -618,71 +604,108 @@ impl WfWellKnownGoals { )) } - /// Computes a goal to prove constraints on a Copy implementation. + /// Verify constraints on a Copy implementation. /// Copy impl is considered well-formed for /// a) certain builtin types (scalar values, shared ref, etc..) /// b) adts which /// 1) have all Copy fields /// 2) don't have a Drop impl fn copy_impl_constraint( + solver: &mut dyn Solver, db: &dyn RustIrDatabase, - trait_ref: &TraitRef, - ) -> Option> { + impl_datum: &ImplDatum, + ) -> bool { let interner = db.interner(); - let ty = trait_ref.self_type_parameter(interner); - let ty_data = ty.data(interner); + let mut gb = GoalBuilder::new(db); + + let impl_fields = impl_datum + .binders + .map_ref(|v| (&v.trait_ref, &v.where_clauses)); // Implementations for scalars, pointer types and never type are provided by libcore. // User implementations on types other than ADTs are forbidden. - let (adt_id, substitution) = match ty_data { - TyData::Apply(ApplicationTy { name, substitution }) => match name { + match impl_datum + .binders + .skip_binders() + .trait_ref + .self_type_parameter(interner) + .data(interner) + { + TyData::Apply(ApplicationTy { name, .. }) => match name { TypeName::Scalar(_) | TypeName::Raw(_) | TypeName::Ref(Mutability::Not) - | TypeName::Never => return None, - TypeName::Adt(adt_id) => (*adt_id, substitution), - _ => return Some(GoalData::CannotProve.intern(interner)), + | TypeName::Never => return true, + TypeName::Adt(_) => (), + _ => return false, }, - _ => return Some(GoalData::CannotProve.intern(interner)), + _ => return false, }; - // not { Implemented(ImplSelfTy: Drop) } - let neg_drop_goal = db - .well_known_trait_id(WellKnownTrait::Drop) - .map(|drop_trait_id| { - TraitRef { - trait_id: drop_trait_id, - substitution: Substitution::from1(interner, ty.clone()), - } - .cast::>(interner) - .negate(interner) - }); + // Well fomedness goal for ADTs + let well_formed_goal = + gb.forall(&impl_fields, (), |gb, _, (trait_ref, where_clauses), ()| { + let interner = gb.interner(); - let adt_datum = db.adt_datum(adt_id); + let ty = trait_ref.self_type_parameter(interner); + let ty_data = ty.data(interner); - let goals = adt_datum - .binders - .map_ref(|b| &b.variants) - .substitute(interner, substitution) - .into_iter() - .flat_map(|v| { - v.fields.into_iter().map(|f| { - // Implemented(FieldTy: Copy) - TraitRef { - trait_id: trait_ref.trait_id, - substitution: Substitution::from1(interner, f), - } - .cast(interner) - }) - }) - .chain(neg_drop_goal.into_iter()); + let (adt_id, substitution) = match ty_data { + TyData::Apply(ApplicationTy { name, substitution }) => match name { + TypeName::Adt(adt_id) => (*adt_id, substitution), + _ => unreachable!(), + }, + + _ => unreachable!(), + }; + + // if (WC) { ... } + gb.implies( + impl_wf_environment(interner, &where_clauses, &trait_ref), + |gb| -> Goal { + let db = gb.db(); + + // not { Implemented(ImplSelfTy: Drop) } + let neg_drop_goal = + db.well_known_trait_id(WellKnownTrait::Drop) + .map(|drop_trait_id| { + TraitRef { + trait_id: drop_trait_id, + substitution: Substitution::from1(interner, ty.clone()), + } + .cast::>(interner) + .negate(interner) + }); + + let adt_datum = db.adt_datum(adt_id); + + let goals = adt_datum + .binders + .map_ref(|b| &b.variants) + .substitute(interner, substitution) + .into_iter() + .flat_map(|v| { + v.fields.into_iter().map(|f| { + // Implemented(FieldTy: Copy) + TraitRef { + trait_id: trait_ref.trait_id, + substitution: Substitution::from1(interner, f), + } + .cast(interner) + }) + }) + .chain(neg_drop_goal.into_iter()); + gb.all(goals) + }, + ) + }); - Some(Goal::all(interner, goals)) + solver.has_unique_solution(db, &well_formed_goal.into_closed_goal(interner)) } - /// Computes goal to prove constraints on a Drop implementation + /// Verifies constraints on a Drop implementation /// Drop implementation is considered well-formed if: /// a) it's implemented on an ADT /// b) The generic parameters of the impl's type must all be parameters @@ -717,15 +740,16 @@ impl WfWellKnownGoals { /// } /// ``` fn drop_impl_constraint( + solver: &mut dyn Solver, db: &dyn RustIrDatabase, impl_datum: &ImplDatum, - ) -> Option> { + ) -> bool { let interner = db.interner(); let adt_id = match impl_datum.self_type_adt_id(interner) { Some(id) => id, // Drop can only be implemented on a nominal type - None => return Some(GoalData::CannotProve.intern(interner)), + None => return false, }; let mut gb = GoalBuilder::new(db); @@ -791,6 +815,189 @@ impl WfWellKnownGoals { }, ); - Some(gb.all([implied_by_adt_def_goal, eq_goal].iter())) + let well_formed_goal = gb.all([implied_by_adt_def_goal, eq_goal].iter()); + + solver.has_unique_solution(db, &well_formed_goal.into_closed_goal(interner)) + } + + /// Verify constraints a CoerceUnsized impl. + /// Rules for CoerceUnsized impl to be considered well-formed: + /// a) pointer conversions: &[mut] T -> &[mut] U, &[mut] T -> *[mut] U, + /// *[mut] T -> *[mut] U are considered valid if + /// 1) T: Unsize + /// 2) mutability is respected, i.e. immutable -> immutable, mutable -> immutable, + /// mutable -> mutable conversions are allowed, immutable -> mutable is not. + /// b) struct conversions of structures with the same definition, `S` -> `S`. + /// To check if this impl is legal, we would walk down the fields of `S` + /// and consider their types with both substitutes. We are looking to find + /// exactly one (non-phantom) field that has changed its type (from T to U), and + /// expect T to be unsizeable to U, i.e. T: CoerceUnsized. + /// + /// As an example, consider a struct + /// ```rust + /// struct Foo { + /// extra: T, + /// ptr: *mut U, + /// } + /// ``` + /// + /// We might have an impl that allows (e.g.) `Foo` to be unsized + /// to `Foo`. That impl would look like: + /// ```rust,ignore + /// impl, V> CoerceUnsized> for Foo {} + /// ``` + /// In this case: + /// + /// - `extra` has type `T` before and type `T` after + /// - `ptr` has type `*mut U` before and type `*mut V` after + /// + /// Since just one field changed, we would then check that `*mut U: CoerceUnsized<*mut V>` + /// is implemented. This will work out because `U: Unsize`, and we have a libcore rule + /// that `*mut U` can be coerced to `*mut V` if `U: Unsize`. + fn coerce_unsized_impl_constraint( + solver: &mut dyn Solver, + db: &dyn RustIrDatabase, + impl_datum: &ImplDatum, + ) -> bool { + let interner = db.interner(); + let mut gb = GoalBuilder::new(db); + + let (binders, impl_datum) = impl_datum.binders.as_ref().into(); + + let trait_ref: &TraitRef = &impl_datum.trait_ref; + + let source = trait_ref.self_type_parameter(interner); + let target = trait_ref + .substitution + .at(interner, 1) + .assert_ty_ref(interner) + .clone(); + + let mut place_in_environment = |goal| -> Goal { + gb.forall( + &Binders::new( + binders.clone(), + (goal, trait_ref, &impl_datum.where_clauses), + ), + (), + |gb, _, (goal, trait_ref, where_clauses), ()| { + let interner = gb.interner(); + gb.implies( + impl_wf_environment(interner, &where_clauses, &trait_ref), + |_| goal, + ) + }, + ) + }; + + match (source.data(interner), target.data(interner)) { + (TyData::Apply(source_app), TyData::Apply(target_app)) => { + match (&source_app.name, &target_app.name) { + (TypeName::Ref(s_m), TypeName::Ref(t_m)) + | (TypeName::Ref(s_m), TypeName::Raw(t_m)) + | (TypeName::Raw(s_m), TypeName::Raw(t_m)) => { + if (*s_m, *t_m) == (Mutability::Not, Mutability::Mut) { + return false; + } + + let source = source_app.first_type_parameter(interner).unwrap(); + let target = target_app.first_type_parameter(interner).unwrap(); + + let unsize_trait_id = + if let Some(id) = db.well_known_trait_id(WellKnownTrait::Unsize) { + id + } else { + return false; + }; + + // Source: Unsize + let unsize_goal: Goal = TraitRef { + trait_id: unsize_trait_id, + substitution: Substitution::from_iter( + interner, + [source, target].iter().cloned(), + ), + } + .cast(interner); + + // ImplEnv -> Source: Unsize + let unsize_goal = place_in_environment(unsize_goal); + + solver.has_unique_solution(db, &unsize_goal.into_closed_goal(interner)) + } + (TypeName::Adt(source_id), TypeName::Adt(target_id)) => { + let adt_datum = db.adt_datum(*source_id); + + if source_id != target_id || adt_datum.kind != AdtKind::Struct { + return false; + } + + let fields = adt_datum + .binders + .map_ref(|bound| &bound.variants.last().unwrap().fields); + + let (source_fields, target_fields) = ( + fields.substitute(interner, &source_app.substitution), + fields.substitute(interner, &target_app.substitution), + ); + + // collect fields with unequal ids + let uneq_field_ids: Vec = (0..source_fields.len()) + .filter(|&i| { + // ignore phantom data fields + if let Some(adt_id) = source_fields[i].adt_id(interner) { + if db.adt_datum(adt_id).flags.phantom_data { + return false; + } + } + + let eq_goal: Goal = EqGoal { + a: source_fields[i].clone().cast(interner), + b: target_fields[i].clone().cast(interner), + } + .cast(interner); + + // ImplEnv -> Source.fields[i] = Target.fields[i] + let eq_goal = place_in_environment(eq_goal); + + // We are interested in !UNEQUAL! fields + !solver.has_unique_solution(db, &eq_goal.into_closed_goal(interner)) + }) + .collect(); + + if uneq_field_ids.len() != 1 { + return false; + } + + let field_id = uneq_field_ids[0]; + + // Source.fields[i]: CoerceUnsized + let coerce_unsized_goal: Goal = TraitRef { + trait_id: trait_ref.trait_id, + substitution: Substitution::from_iter( + interner, + [ + source_fields[field_id].clone(), + target_fields[field_id].clone(), + ] + .iter() + .cloned(), + ), + } + .cast(interner); + + // ImplEnv -> Source.fields[i]: CoerceUnsized + let coerce_unsized_goal = place_in_environment(coerce_unsized_goal); + + solver.has_unique_solution( + db, + &coerce_unsized_goal.into_closed_goal(interner), + ) + } + _ => false, + } + } + _ => false, + } } } diff --git a/tests/test/wf_lowering.rs b/tests/test/wf_lowering.rs index 9943238ec97..fa7a250cc91 100644 --- a/tests/test/wf_lowering.rs +++ b/tests/test/wf_lowering.rs @@ -1129,3 +1129,312 @@ fn ill_formed_opaque_ty() { } } } + +#[test] +fn coerce_unsized_pointer() { + lowering_success! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a, T, U> CoerceUnsized<*mut U> for &'a mut T where T: Unsize {} + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + } + } + + // T: Unsize is not in the environment + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a, T, U> CoerceUnsized<*mut U> for &'a mut T {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Test with builtin Unsize impl + lowering_success! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + #[object_safe] + trait Foo {} + + #[auto] + #[object_safe] + trait Auto {} + + impl<'a> CoerceUnsized<&'a (dyn Foo + 'a)> for &'a (dyn Foo + Auto + 'a) {} + } + } + + // Test with builtin Unsize impl + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + #[object_safe] + trait Foo {} + + #[auto] + #[object_safe] + trait Auto {} + + impl<'a> CoerceUnsized<&'a (dyn Foo + Auto + 'a)> for &'a (dyn Foo + 'a) {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Test with builtin Unsize impl + lowering_success! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a> CoerceUnsized<&'a [f32]> for &'a [f32; 3] {} + } + } + + // Coercing from shared to mut + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a, T, U> CoerceUnsized<*mut U> for &'a T where T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Coercing from shared to mut + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a, T, U> CoerceUnsized<&'a mut U> for &'a T where T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Coercing from shared to mut + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl CoerceUnsized<*mut U> for *const T where T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Coercing from raw pointer to ref + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + impl<'a, T, U> CoerceUnsized<&'a U> for *const T where T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } +} + +#[test] +fn coerce_unsized_struct() { + lowering_success! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + struct Foo<'a, T> { + t: &'a T + } + + struct Bar { + extra: T, + ptr: *mut U, + } + + impl<'a, T, U> CoerceUnsized<&'a U> for &'a T where T: Unsize {} + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + impl<'a> CoerceUnsized> for Foo<'a, [u32; 3]> {} + impl CoerceUnsized> for Bar where U: Unsize {} + } + } + + // Unsizing different structs + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + struct S1 { + t: T, + } + + struct S2 { + t: T, + } + + impl CoerceUnsized> for S1 where T: CoerceUnsized {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Unsizing enums + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + enum Foo { + A { + t: T + } + } + + impl CoerceUnsized> for Foo where T: CoerceUnsized {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Unsizing two fields + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + struct Bar { + ptr1: *mut T, + ptr2: *mut U, + } + + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + impl CoerceUnsized> for Bar where U: Unsize, T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Unsizing no fields + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + struct Bar { + ptr1: *mut T, + ptr2: *mut U, + } + + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + impl CoerceUnsized> for Bar where T: Unsize {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // No unsize in the environment + lowering_error! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + struct Bar { + extra: T, + ptr: *mut U, + } + + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + impl CoerceUnsized> for Bar {} + } error_msg { + "trait impl for `CoerceUnsized` does not meet well-formedness requirements" + } + } + + // Phantom data test & CoerceUnsized in the environment test + lowering_success! { + program { + #[lang(unsize)] + trait Unsize {} + + #[lang(coerce_unsized)] + trait CoerceUnsized {} + + #[phantom_data] + struct PhantomData {} + + struct Foo { + coerce: T, + phantom: PhantomData, + } + + struct Bar { + extra: T, + phantom: PhantomData, + ptr: *mut U, + } + + impl CoerceUnsized<*mut U> for *mut T where T: Unsize {} + impl CoerceUnsized> for Bar where U: Unsize {} + impl CoerceUnsized> for Foo where T: CoerceUnsized {} + } + } +}