Properly generalize unevaluated consts

This commit is contained in:
khyperia
2026-03-30 15:47:49 +02:00
parent ac40f5e105
commit 75eeece98c
13 changed files with 312 additions and 155 deletions
@@ -61,103 +61,13 @@ pub fn instantiate_ty_var<R: PredicateEmittingRelation<InferCtxt<'tcx>>>(
) -> RelateResult<'tcx, ()> {
debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown());
// Generalize `source_ty` depending on the current variance. As an example, assume
// `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference
// variable.
//
// Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh
// region/type inference variables.
//
// We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and
// `?1 <: ?3`.
let Generalization { value_may_be_infer: generalized_ty } = self.generalize(
relation.span(),
relation.structurally_relate_aliases(),
target_vid,
self.instantiate_var(
relation,
target_is_expected,
target_vid.into(),
instantiation_variance,
source_ty,
)?;
// Constrain `b_vid` to the generalized type `generalized_ty`.
if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() {
self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid);
} else {
self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty);
}
// Finally, relate `generalized_ty` to `source_ty`, as described in previous comment.
//
// FIXME(#16847): This code is non-ideal because all these subtype
// relations wind up attributed to the same spans. We need
// to associate causes/spans with each of the relations in
// the stack to get this right.
if generalized_ty.is_ty_var() {
// This happens for cases like `<?0 as Trait>::Assoc == ?0`.
// We can't instantiate `?0` here as that would result in a
// cyclic type. We instead delay the unification in case
// the alias can be normalized to something which does not
// mention `?0`.
if self.next_trait_solver() {
let (lhs, rhs, direction) = match instantiation_variance {
ty::Invariant => {
(generalized_ty.into(), source_ty.into(), AliasRelationDirection::Equate)
}
ty::Covariant => {
(generalized_ty.into(), source_ty.into(), AliasRelationDirection::Subtype)
}
ty::Contravariant => {
(source_ty.into(), generalized_ty.into(), AliasRelationDirection::Subtype)
}
ty::Bivariant => unreachable!("bivariant generalization"),
};
relation.register_predicates([ty::PredicateKind::AliasRelate(lhs, rhs, direction)]);
} else {
match source_ty.kind() {
&ty::Alias(ty::Projection, data) => {
// FIXME: This does not handle subtyping correctly, we could
// instead create a new inference variable `?normalized_source`, emitting
// `Projection(normalized_source, ?ty_normalized)` and
// `?normalized_source <: generalized_ty`.
relation.register_predicates([ty::ProjectionPredicate {
projection_term: data.into(),
term: generalized_ty.into(),
}]);
}
// The old solver only accepts projection predicates for associated types.
ty::Alias(ty::Inherent | ty::Free | ty::Opaque, _) => {
return Err(TypeError::CyclicTy(source_ty));
}
_ => bug!("generalized `{source_ty:?} to infer, not an alias"),
}
}
} else {
// NOTE: The `instantiation_variance` is not the same variance as
// used by the relation. When instantiating `b`, `target_is_expected`
// is flipped and the `instantiation_variance` is also flipped. To
// constrain the `generalized_ty` while using the original relation,
// we therefore only have to flip the arguments.
//
// ```ignore (not code)
// ?a rel B
// instantiate_ty_var(?a, B) # expected and variance not flipped
// B' rel B
// ```
// or
// ```ignore (not code)
// A rel ?b
// instantiate_ty_var(?b, A) # expected and variance flipped
// A rel A'
// ```
if target_is_expected {
relation.relate(generalized_ty, source_ty)?;
} else {
debug!("flip relation");
relation.relate(source_ty, generalized_ty)?;
}
}
Ok(())
source_ty.into(),
)
}
/// Instantiates the const variable `target_vid` with the given constant.
@@ -204,54 +114,191 @@ pub(crate) fn instantiate_const_var<R: PredicateEmittingRelation<InferCtxt<'tcx>
) -> RelateResult<'tcx, ()> {
// FIXME(generic_const_exprs): Occurs check failures for unevaluated
// constants and generic expressions are not yet handled correctly.
let Generalization { value_may_be_infer: generalized_ct } = self.generalize(
debug_assert!(
self.inner.borrow_mut().const_unification_table().probe_value(target_vid).is_unknown()
);
self.instantiate_var(
relation,
target_is_expected,
target_vid.into(),
ty::Invariant,
source_ct.into(),
)
}
#[instrument(level = "debug", skip(self, relation))]
fn instantiate_var<R: PredicateEmittingRelation<Self>>(
&self,
relation: &mut R,
target_is_expected: bool,
target_vid: TermVid,
instantiation_variance: ty::Variance,
source_term: Term<'tcx>,
) -> RelateResult<'tcx, ()> {
// Generalize `source_term` depending on the current variance. As an example, assume
// `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference
// variable.
//
// Then the `generalized_term` would be `&'?2 ?3`, where `'?2` and `?3` are fresh
// region/type inference variables.
//
// We then relate `generalized_term <: source_term`, adding constraints like `'x: '?2` and
// `?1 <: ?3`.
let Generalization { value_may_be_infer: generalized_term } = self.generalize(
relation.span(),
relation.structurally_relate_aliases(),
target_vid,
ty::Invariant,
source_ct,
instantiation_variance,
source_term,
)?;
debug_assert!(!generalized_ct.is_ct_infer());
// Constrain `b_vid` to the generalized type `generalized_term`.
self.union_var_term(target_vid, generalized_term);
self.inner
.borrow_mut()
.const_unification_table()
.union_value(target_vid, ConstVariableValue::Known { value: generalized_ct });
// Finally, relate `generalized_term` to `source_term`, as described in previous comment.
//
// FIXME(#16847): This code is non-ideal because all these subtype
// relations wind up attributed to the same spans. We need
// to associate causes/spans with each of the relations in
// the stack to get this right.
if generalized_term.is_infer() {
// This happens for cases like `<?0 as Trait>::Assoc == ?0`.
// We can't instantiate `?0` here as that would result in a
// cyclic type. We instead delay the unification in case
// the alias can be normalized to something which does not
// mention `?0`.
if self.next_trait_solver() {
let (lhs, rhs, direction) = match instantiation_variance {
ty::Invariant => {
(generalized_term, source_term, AliasRelationDirection::Equate)
}
ty::Covariant => {
(generalized_term, source_term, AliasRelationDirection::Subtype)
}
ty::Contravariant => {
(source_term, generalized_term, AliasRelationDirection::Subtype)
}
ty::Bivariant => unreachable!("bivariant generalization"),
};
// Make sure that the order is correct when relating the
// generalized const and the source.
if target_is_expected {
relation.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
generalized_ct,
source_ct,
)?;
relation.register_predicates([ty::PredicateKind::AliasRelate(lhs, rhs, direction)]);
} else {
let Some(source_alias) = source_term.to_alias_term() else {
bug!("generalized `{source_term:?} to infer, not an alias");
};
match source_alias.kind(self.tcx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
// FIXME: This does not handle subtyping correctly, we could
// instead create a new inference variable `?normalized_source`, emitting
// `Projection(normalized_source, ?ty_normalized)` and
// `?normalized_source <: generalized_term`.
relation.register_predicates([ty::ProjectionPredicate {
projection_term: source_alias,
term: generalized_term,
}]);
}
// The old solver only accepts projection predicates for associated types.
ty::AliasTermKind::InherentTy
| ty::AliasTermKind::FreeTy
| ty::AliasTermKind::OpaqueTy => {
return Err(TypeError::CyclicTy(source_term.expect_type()));
}
ty::AliasTermKind::InherentConst
| ty::AliasTermKind::FreeConst
| ty::AliasTermKind::UnevaluatedConst => {
return Err(TypeError::CyclicConst(source_term.expect_const()));
}
}
}
} else {
relation.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
source_ct,
generalized_ct,
)?;
// NOTE: The `instantiation_variance` is not the same variance as
// used by the relation. When instantiating `b`, `target_is_expected`
// is flipped and the `instantiation_variance` is also flipped. To
// constrain the `generalized_term` while using the original relation,
// we therefore only have to flip the arguments.
//
// ```ignore (not code)
// ?a rel B
// instantiate_ty_var(?a, B) # expected and variance not flipped
// B' rel B
// ```
// or
// ```ignore (not code)
// A rel ?b
// instantiate_ty_var(?b, A) # expected and variance flipped
// A rel A'
// ```
match generalized_term.kind() {
ty::TermKind::Ty(_) => {
if target_is_expected {
relation.relate(generalized_term, source_term)?;
} else {
debug!("flip relation");
relation.relate(source_term, generalized_term)?;
}
}
ty::TermKind::Const(_) => {
// Override consts to always be invariant
if target_is_expected {
relation.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
generalized_term,
source_term,
)?;
} else {
relation.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
source_term,
generalized_term,
)?;
}
}
}
}
Ok(())
}
/// This is a thin wrapper around inserting into the var tables. You probably want
/// [`Self::instantiate_var`] instead, which calls this method.
fn union_var_term(&self, l: TermVid, r: ty::Term<'tcx>) {
match (l, r.kind()) {
(TermVid::Ty(l), ty::TermKind::Ty(r)) => {
if let Some(r) = r.ty_vid() {
self.inner.borrow_mut().type_variables().equate(l, r)
} else {
self.inner.borrow_mut().type_variables().instantiate(l, r)
}
}
(TermVid::Const(l), ty::TermKind::Const(r)) => {
if let Some(r) = r.ct_vid() {
self.inner.borrow_mut().const_unification_table().union(l, r)
} else {
self.inner
.borrow_mut()
.const_unification_table()
.union_value(l, ConstVariableValue::Known { value: r })
}
}
_ => bug!("mismatched term kinds in generalize: {l:?}, {r:?}"),
}
}
/// Attempts to generalize `source_term` for the type variable `target_vid`.
/// This checks for cycles -- that is, whether `source_term` references `target_vid`.
fn generalize<T: Into<Term<'tcx>> + Relate<TyCtxt<'tcx>>>(
fn generalize(
&self,
span: Span,
structurally_relate_aliases: StructurallyRelateAliases,
target_vid: impl Into<TermVid>,
target_vid: TermVid,
ambient_variance: ty::Variance,
source_term: T,
) -> RelateResult<'tcx, Generalization<T>> {
source_term: Term<'tcx>,
) -> RelateResult<'tcx, Generalization<Term<'tcx>>> {
assert!(!source_term.has_escaping_bound_vars());
let (for_universe, root_vid) = match target_vid.into() {
let (for_universe, root_vid) = match target_vid {
TermVid::Ty(ty_vid) => {
(self.probe_ty_var(ty_vid).unwrap_err(), TermVid::Ty(self.root_var(ty_vid)))
}
@@ -267,7 +314,7 @@ fn generalize<T: Into<Term<'tcx>> + Relate<TyCtxt<'tcx>>>(
structurally_relate_aliases,
root_vid,
for_universe,
root_term: source_term.into(),
root_term: source_term,
ambient_variance,
in_alias: false,
cache: Default::default(),
@@ -377,8 +424,12 @@ fn cyclic_term_error(&self) -> TypeError<'tcx> {
/// Create a new type variable in the universe of the target when
/// generalizing an alias.
fn next_ty_var_for_alias(&self) -> Ty<'tcx> {
self.infcx.next_ty_var_in_universe(self.span, self.for_universe)
fn next_var_for_alias_of_kind(&self, alias: ty::AliasTerm<'tcx>) -> ty::Term<'tcx> {
if alias.kind(self.cx()).is_type() {
self.infcx.next_ty_var_in_universe(self.span, self.for_universe).into()
} else {
self.infcx.next_const_var_in_universe(self.span, self.for_universe).into()
}
}
/// An occurs check failure inside of an alias does not mean
@@ -399,10 +450,10 @@ fn next_ty_var_for_alias(&self) -> Ty<'tcx> {
/// continue generalizing the alias. This ends up pulling down the universe of the
/// inference variable and is incomplete in case the alias would normalize to a type
/// which does not mention that inference variable.
fn generalize_alias_ty(
fn generalize_alias_term(
&mut self,
alias: ty::AliasTy<'tcx>,
) -> Result<Ty<'tcx>, TypeError<'tcx>> {
alias: ty::AliasTerm<'tcx>,
) -> Result<Term<'tcx>, TypeError<'tcx>> {
// We do not eagerly replace aliases with inference variables if they have
// escaping bound vars, see the method comment for details. However, when we
// are inside of an alias with escaping bound vars replacing nested aliases
@@ -410,12 +461,12 @@ fn generalize_alias_ty(
//
// cc trait-system-refactor-initiative#110
if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() && !self.in_alias {
return Ok(self.next_ty_var_for_alias());
return Ok(self.next_var_for_alias_of_kind(alias));
}
let is_nested_alias = mem::replace(&mut self.in_alias, true);
let result = match self.relate(alias, alias) {
Ok(alias) => Ok(alias.to_ty(self.cx())),
Ok(alias) => Ok(alias.to_term(self.cx())),
Err(e) => {
if is_nested_alias {
return Err(e);
@@ -430,7 +481,7 @@ fn generalize_alias_ty(
}
debug!("generalization failure in alias");
Ok(self.next_ty_var_for_alias())
Ok(self.next_var_for_alias_of_kind(alias))
}
}
};
@@ -585,7 +636,9 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
}
ty::Alias(_, data) => match self.structurally_relate_aliases {
StructurallyRelateAliases::No => self.generalize_alias_ty(data),
StructurallyRelateAliases::No => {
self.generalize_alias_term(data.into()).map(|v| v.expect_type())
}
StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t),
},
@@ -695,17 +748,26 @@ fn consts(
// FIXME: Unevaluated constants are also not rigid, so the current
// approach of always relating them structurally is incomplete.
//
// FIXME: remove this branch once `structurally_relate_consts` is fully
// structural.
ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, args }) => {
let args = self.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
args,
args,
)?;
Ok(ty::Const::new_unevaluated(self.cx(), ty::UnevaluatedConst { def, args }))
}
// FIXME: replace the StructurallyRelateAliases::Yes branch with
// `structurally_relate_consts` once it is fully structural.
ty::ConstKind::Unevaluated(uv) => match self.structurally_relate_aliases {
// Hack: Fall back to old behavior if GCE is enabled (it used to just be the Yes
// path), as doing this new No path breaks some GCE things. I expect GCE to be
// ripped out soon so this shouldn't matter soon.
StructurallyRelateAliases::No if !self.cx().features().generic_const_exprs() => {
self.generalize_alias_term(uv.into()).map(|v| v.expect_const())
}
_ => {
let ty::UnevaluatedConst { def, args } = uv;
let args = self.relate_with_variance(
ty::Invariant,
ty::VarianceDiagInfo::default(),
args,
args,
)?;
Ok(ty::Const::new_unevaluated(self.cx(), ty::UnevaluatedConst { def, args }))
}
},
ty::ConstKind::Placeholder(placeholder) => {
if self.for_universe.can_name(placeholder.universe) {
Ok(c)
@@ -110,6 +110,13 @@ pub(crate) fn known(&self) -> Option<ty::Const<'tcx>> {
ConstVariableValue::Known { value } => Some(value),
}
}
pub(crate) fn is_unknown(&self) -> bool {
match *self {
ConstVariableValue::Unknown { .. } => true,
ConstVariableValue::Known { .. } => false,
}
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
+7
View File
@@ -323,6 +323,13 @@ pub fn is_ct_infer(self) -> bool {
matches!(self.kind(), ty::ConstKind::Infer(_))
}
pub fn ct_vid(self) -> Option<ty::ConstVid> {
match self.kind() {
ConstKind::Infer(ty::InferConst::Var(vid)) => Some(vid),
_ => None,
}
}
/// Iterator that walks `self` and any types reachable from
/// `self`, in depth-first order. Note that just walks the types
/// that appear in `self`, it does not descend into the fields of
@@ -1,5 +1,5 @@
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
error[E0119]: conflicting implementations of trait `Overlap<for<'a> fn(&'a (), ())>` for type `for<'a> fn(&'a (), ())`
--> $DIR/associated-type.rs:32:1
|
@@ -1,5 +1,5 @@
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit), .. }
error[E0119]: conflicting implementations of trait `Overlap<for<'a> fn(&'a (), ())>` for type `for<'a> fn(&'a (), ())`
--> $DIR/associated-type.rs:32:1
|
@@ -0,0 +1,13 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/153831>
//@ check-fail
//@compile-flags: -Znext-solver=globally --emit=obj
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
type const A: () = A;
//~^ ERROR type mismatch resolving `A normalizes-to _`
//~| ERROR the constant `A` is not of type `()`
fn main() {
A;
}
@@ -0,0 +1,15 @@
error[E0271]: type mismatch resolving `A normalizes-to _`
--> $DIR/free-const-recursive.rs:7:1
|
LL | type const A: () = A;
| ^^^^^^^^^^^^^^^^ types differ
error: the constant `A` is not of type `()`
--> $DIR/free-const-recursive.rs:7:1
|
LL | type const A: () = A;
| ^^^^^^^^^^^^^^^^ expected `()`, found a different `()`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0271`.
@@ -0,0 +1,19 @@
//! See also <https://github.com/rust-lang/rust/issues/153831>
//@ check-fail
//@compile-flags: -Znext-solver=globally --emit=obj
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait {
type const A: ();
}
impl Trait for () {
type const A: () = <() as Trait>::A;
//~^ ERROR type mismatch resolving `<() as Trait>::A normalizes-to _`
//~| ERROR the constant `<() as Trait>::A` is not of type `()`
}
fn main() {
<() as Trait>::A;
}
@@ -0,0 +1,15 @@
error[E0271]: type mismatch resolving `<() as Trait>::A normalizes-to _`
--> $DIR/projection-const-recursive.rs:12:5
|
LL | type const A: () = <() as Trait>::A;
| ^^^^^^^^^^^^^^^^ types differ
error: the constant `<() as Trait>::A` is not of type `()`
--> $DIR/projection-const-recursive.rs:12:5
|
LL | type const A: () = <() as Trait>::A;
| ^^^^^^^^^^^^^^^^ expected `()`, found a different `()`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0271`.
@@ -1,5 +1,4 @@
// FIXME(ogca): this should ERROR not pass!!
//@ check-pass
//@ check-fail
#![feature(generic_const_items, min_generic_const_args, opaque_generic_const_args)]
#![expect(incomplete_features)]
@@ -12,8 +11,8 @@ trait Trait {}
impl Trait for [(); FOO::<1>] {}
impl Trait for [(); BAR::<1>] {}
// FIXME(ogca): this should ERROR!
//~^ ERROR conflicting implementations of trait `Trait` for type `[(); FOO::<1>]`
impl Trait for [(); BAR::<2>] {}
// FIXME(ogca): this should ERROR!
//~^ ERROR conflicting implementations of trait `Trait` for type `[(); FOO::<1>]`
fn main() {}
@@ -0,0 +1,20 @@
error[E0119]: conflicting implementations of trait `Trait` for type `[(); FOO::<1>]`
--> $DIR/coherence-ambiguous.rs:13:1
|
LL | impl Trait for [(); FOO::<1>] {}
| ----------------------------- first implementation here
LL | impl Trait for [(); BAR::<1>] {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[(); FOO::<1>]`
error[E0119]: conflicting implementations of trait `Trait` for type `[(); FOO::<1>]`
--> $DIR/coherence-ambiguous.rs:15:1
|
LL | impl Trait for [(); FOO::<1>] {}
| ----------------------------- first implementation here
...
LL | impl Trait for [(); BAR::<2>] {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[(); FOO::<1>]`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0119`.
@@ -1,4 +1,4 @@
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: [?1t, '^0.Named(DefId(0:15 ~ structually_relate_aliases[de75]::{impl#1}::'a))], def_id: DefId(0:5 ~ structually_relate_aliases[de75]::ToUnit::Unit), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [?1t, '^0.Named(DefId(0:15 ~ structually_relate_aliases[de75]::{impl#1}::'a))], def_id: DefId(0:5 ~ structually_relate_aliases[de75]::ToUnit::Unit), .. }
error[E0277]: the trait bound `for<'a> T: ToUnit<'a>` is not satisfied
--> $DIR/structually-relate-aliases.rs:13:36
|
@@ -16,7 +16,7 @@ help: this trait has no implementations, consider adding one
LL | trait ToUnit<'a> {
| ^^^^^^^^^^^^^^^^
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTy { args: ['^0.Named(DefId(0:15 ~ issue_118950_root_region[d54f]::{impl#1}::'a)), ?1t], def_id: DefId(0:8 ~ issue_118950_root_region[d54f]::Assoc), .. }
WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: ['^0.Named(DefId(0:15 ~ issue_118950_root_region[d54f]::{impl#1}::'a)), ?1t], def_id: DefId(0:8 ~ issue_118950_root_region[d54f]::Assoc), .. }
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0277, E0425.