1
Fork 0

consistently handle global where-bounds

This commit is contained in:
lcnr 2024-10-29 16:10:13 +01:00
parent 5f548890b8
commit 3350b9faad
6 changed files with 253 additions and 346 deletions

View file

@ -23,6 +23,7 @@
#![feature(extract_if)] #![feature(extract_if)]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(iter_intersperse)] #![feature(iter_intersperse)]
#![feature(iterator_try_reduce)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(never_type)] #![feature(never_type)]
#![feature(rustdoc_internals)] #![feature(rustdoc_internals)]

View file

@ -445,7 +445,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// Winnow, but record the exact outcome of evaluation, which // Winnow, but record the exact outcome of evaluation, which
// is needed for specialization. Propagate overflow if it occurs. // is needed for specialization. Propagate overflow if it occurs.
let mut candidates = candidates let candidates = candidates
.into_iter() .into_iter()
.map(|c| match self.evaluate_candidate(stack, &c) { .map(|c| match self.evaluate_candidate(stack, &c) {
Ok(eval) if eval.may_apply() => { Ok(eval) if eval.may_apply() => {
@ -458,40 +458,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
.flat_map(Result::transpose) .flat_map(Result::transpose)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
debug!(?stack, ?candidates, "winnowed to {} candidates", candidates.len()); debug!(?stack, ?candidates, "{} potentially applicable candidates", candidates.len());
let has_non_region_infer = stack.obligation.predicate.has_non_region_infer();
// If there are STILL multiple candidates, we can further
// reduce the list by dropping duplicates -- including
// resolving specializations.
if candidates.len() > 1 {
let mut i = 0;
while i < candidates.len() {
let should_drop_i = (0..candidates.len()).filter(|&j| i != j).any(|j| {
self.candidate_should_be_dropped_in_favor_of(
&candidates[i],
&candidates[j],
has_non_region_infer,
) == DropVictim::Yes
});
if should_drop_i {
debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len());
candidates.swap_remove(i);
} else {
debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len());
i += 1;
// If there are *STILL* multiple candidates, give up
// and report ambiguity.
if i > 1 {
debug!("multiple matches, ambig");
return Ok(None);
}
}
}
}
// If there are *NO* candidates, then there are no impls -- // If there are *NO* candidates, then there are no impls --
// that we know of, anyway. Note that in the case where there // that we know of, anyway. Note that in the case where there
// are unbound type variables within the obligation, it might // are unbound type variables within the obligation, it might
@ -508,13 +475,18 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// to have emitted at least one. // to have emitted at least one.
if stack.obligation.predicate.references_error() { if stack.obligation.predicate.references_error() {
debug!(?stack.obligation.predicate, "found error type in predicate, treating as ambiguous"); debug!(?stack.obligation.predicate, "found error type in predicate, treating as ambiguous");
return Ok(None); Ok(None)
} else {
Err(Unimplemented)
}
} else {
let has_non_region_infer = stack.obligation.predicate.has_non_region_infer();
if let Some(candidate) = self.winnow_candidates(has_non_region_infer, candidates) {
self.filter_reservation_impls(candidate)
} else {
Ok(None)
} }
return Err(Unimplemented);
} }
// Just one candidate left.
self.filter_reservation_impls(candidates.pop().unwrap().candidate)
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -1803,18 +1775,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum DropVictim {
Yes,
No,
}
impl DropVictim {
fn drop_if(should_drop: bool) -> DropVictim {
if should_drop { DropVictim::Yes } else { DropVictim::No }
}
}
/// ## Winnowing /// ## Winnowing
/// ///
/// Winnowing is the process of attempting to resolve ambiguity by /// Winnowing is the process of attempting to resolve ambiguity by
@ -1822,61 +1782,149 @@ impl DropVictim {
/// type variables and then we also attempt to evaluate recursive /// type variables and then we also attempt to evaluate recursive
/// bounds to see if they are satisfied. /// bounds to see if they are satisfied.
impl<'tcx> SelectionContext<'_, 'tcx> { impl<'tcx> SelectionContext<'_, 'tcx> {
/// Returns `DropVictim::Yes` if `victim` should be dropped in favor of /// If there are multiple ways to prove a trait goal, we make some
/// `other`. Generally speaking we will drop duplicate /// *fairly arbitrary* choices about which candidate is actually used.
/// candidates and prefer where-clause candidates.
/// ///
/// See the comment for "SelectionCandidate" for more details. /// For more details, look at the implementation of this method :)
#[instrument(level = "debug", skip(self))] #[instrument(level = "debug", skip(self), ret)]
fn candidate_should_be_dropped_in_favor_of( fn winnow_candidates(
&mut self, &mut self,
victim: &EvaluatedCandidate<'tcx>,
other: &EvaluatedCandidate<'tcx>,
has_non_region_infer: bool, has_non_region_infer: bool,
) -> DropVictim { mut candidates: Vec<EvaluatedCandidate<'tcx>>,
if victim.candidate == other.candidate { ) -> Option<SelectionCandidate<'tcx>> {
return DropVictim::Yes; if candidates.len() == 1 {
return Some(candidates.pop().unwrap().candidate);
} }
// Check if a bound would previously have been removed when normalizing // We prefer trivial builtin candidates, i.e. builtin impls without any nested
// the param_env so that it can be given the lowest priority. See // requirements, over all others. This is a fix for #53123 and prevents winnowing
// #50825 for the motivation for this. // from accidentally extending the lifetime of a variable.
let is_global = let mut trivial_builtin = candidates
|cand: ty::PolyTraitPredicate<'tcx>| cand.is_global() && !cand.has_bound_vars(); .iter()
.filter(|c| matches!(c.candidate, BuiltinCandidate { has_nested: false }));
if let Some(_trivial) = trivial_builtin.next() {
// There should only ever be a single trivial builtin candidate
// as they would otherwise overlap.
debug_assert_eq!(trivial_builtin.next(), None);
return Some(BuiltinCandidate { has_nested: false });
}
// (*) Prefer `BuiltinCandidate { has_nested: false }`, `PointeeCandidate`, // Before we consider where-bounds, we have to deduplicate them here and also
// or `DiscriminantKindCandidate` to anything else. // drop where-bounds in case the same where-bound exists without bound vars.
// This is necessary as elaborating super-trait bounds may result in duplicates.
'search_victim: loop {
for (i, this) in candidates.iter().enumerate() {
let ParamCandidate(this) = this.candidate else { continue };
for (j, other) in candidates.iter().enumerate() {
if i == j {
continue;
}
let ParamCandidate(other) = other.candidate else { continue };
if this == other {
candidates.remove(j);
continue 'search_victim;
}
if this.skip_binder().trait_ref == other.skip_binder().trait_ref
&& this.skip_binder().polarity == other.skip_binder().polarity
&& !this.skip_binder().trait_ref.has_escaping_bound_vars()
{
candidates.remove(j);
continue 'search_victim;
}
}
}
break;
}
// The next highest priority is for non-global where-bounds. However, while we don't
// prefer global where-clauses here, we do bail with ambiguity when encountering both
// a global and a non-global where-clause.
// //
// This is a fix for #53123 and prevents winnowing from accidentally extending the // Our handling of where-bounds is generally fairly messy but necessary for backwards
// lifetime of a variable. // compatability, see #50825 for why we need to handle global where-bounds like this.
match (&other.candidate, &victim.candidate) { let is_global = |c: ty::PolyTraitPredicate<'tcx>| c.is_global() && !c.has_bound_vars();
// FIXME(@jswrenn): this should probably be more sophisticated let param_candidates = candidates
(TransmutabilityCandidate, _) | (_, TransmutabilityCandidate) => DropVictim::No, .iter()
.filter_map(|c| if let ParamCandidate(p) = c.candidate { Some(p) } else { None });
let mut has_global_bounds = false;
let mut param_candidate = None;
for c in param_candidates {
if is_global(c) {
has_global_bounds = true;
} else if param_candidate.replace(c).is_some() {
// Ambiguity, two potentially different where-clauses
return None;
}
}
if let Some(predicate) = param_candidate {
// Ambiguity, a global and a non-global where-bound.
if has_global_bounds {
return None;
} else {
return Some(ParamCandidate(predicate));
}
}
// (*) // Prefer alias-bounds over blanket impls for rigid associated types. This is
(BuiltinCandidate { has_nested: false }, _) => DropVictim::Yes, // fairly arbitrary but once again necessary for backwards compatibility.
(_, BuiltinCandidate { has_nested: false }) => DropVictim::No, // If there are multiple applicable candidates which don't affect type inference,
// choose the one with the lowest index.
let alias_bound = candidates
.iter()
.filter_map(|c| if let ProjectionCandidate(i) = c.candidate { Some(i) } else { None })
.try_reduce(|c1, c2| if has_non_region_infer { None } else { Some(c1.min(c2)) });
match alias_bound {
Some(Some(index)) => return Some(ProjectionCandidate(index)),
Some(None) => {}
None => return None,
}
(ParamCandidate(other), ParamCandidate(victim)) => { // Need to prioritize builtin trait object impls as `<dyn Any as Any>::type_id`
let same_except_bound_vars = other.skip_binder().trait_ref // should use the vtable method and not the method provided by the user-defined
== victim.skip_binder().trait_ref // impl `impl<T: ?Sized> Any for T { .. }`. This really shouldn't exist but is
&& other.skip_binder().polarity == victim.skip_binder().polarity // necessary due to #57893. We again arbitrarily prefer the applicable candidate
&& !other.skip_binder().trait_ref.has_escaping_bound_vars(); // with the lowest index.
if same_except_bound_vars { let object_bound = candidates
// See issue #84398. In short, we can generate multiple ParamCandidates which are .iter()
// the same except for unused bound vars. Just pick the one with the fewest bound vars .filter_map(|c| if let ObjectCandidate(i) = c.candidate { Some(i) } else { None })
// or the current one if tied (they should both evaluate to the same answer). This is .try_reduce(|c1, c2| if has_non_region_infer { None } else { Some(c1.min(c2)) });
// probably best characterized as a "hack", since we might prefer to just do our match object_bound {
// best to *not* create essentially duplicate candidates in the first place. Some(Some(index)) => return Some(ObjectCandidate(index)),
DropVictim::drop_if(other.bound_vars().len() <= victim.bound_vars().len()) Some(None) => {}
None => return None,
}
// Finally, handle overlapping user-written impls.
let impls = candidates.iter().filter_map(|c| {
if let ImplCandidate(def_id) = c.candidate {
Some((def_id, c.evaluation))
} else {
None
}
});
let mut impl_candidate = None;
for c in impls {
if let Some(prev) = impl_candidate.replace(c) {
if self.prefer_lhs_over_victim(has_non_region_infer, c, prev) {
// Ok, prefer `c` over the previous entry
} else if self.prefer_lhs_over_victim(has_non_region_infer, prev, c) {
// Ok, keep `prev` instead of the new entry
impl_candidate = Some(prev);
} else { } else {
DropVictim::No // Ambiguity, two potentially different where-clauses
return None;
} }
} }
}
( if let Some((def_id, _evaluation)) = impl_candidate {
ParamCandidate(other_cand), // Don't use impl candidates which overlap with other candidates.
ImplCandidate(..) // This should pretty much only ever happen with malformed impls.
if candidates.iter().all(|c| match c.candidate {
BuiltinCandidate { has_nested: _ }
| TransmutabilityCandidate
| AutoImplCandidate | AutoImplCandidate
| ClosureCandidate { .. } | ClosureCandidate { .. }
| AsyncClosureCandidate | AsyncClosureCandidate
@ -1885,225 +1933,113 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| FutureCandidate | FutureCandidate
| IteratorCandidate | IteratorCandidate
| AsyncIteratorCandidate | AsyncIteratorCandidate
| FnPointerCandidate { .. } | FnPointerCandidate
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| TraitAliasCandidate | TraitAliasCandidate
| ObjectCandidate(_) | TraitUpcastingUnsizeCandidate(_)
| ProjectionCandidate(_), | BuiltinObjectCandidate
) => { | BuiltinUnsizeCandidate => false,
// We have a where clause so don't go around looking // Non-global param candidates have already been handled, global
// for impls. Arbitrarily give param candidates priority // where-bounds get ignored.
// over projection and object candidates. ParamCandidate(_) | ImplCandidate(_) => true,
ProjectionCandidate(_) | ObjectCandidate(_) => unreachable!(),
}) {
return Some(ImplCandidate(def_id));
} else {
return None;
}
}
if candidates.len() == 1 {
Some(candidates.pop().unwrap().candidate)
} else {
// Also try ignoring all global where-bounds and check whether we end
// with a unique candidate in this case.
let mut not_a_global_where_bound = candidates
.into_iter()
.filter(|c| !matches!(c.candidate, ParamCandidate(p) if is_global(p)));
not_a_global_where_bound
.next()
.map(|c| c.candidate)
.filter(|_| not_a_global_where_bound.next().is_none())
}
}
fn prefer_lhs_over_victim(
&self,
has_non_region_infer: bool,
(lhs, lhs_evaluation): (DefId, EvaluationResult),
(victim, victim_evaluation): (DefId, EvaluationResult),
) -> bool {
let tcx = self.tcx();
// See if we can toss out `victim` based on specialization.
//
// While this requires us to know *for sure* that the `lhs` impl applies
// we still use modulo regions here. This is fine as specialization currently
// assumes that specializing impls have to be always applicable, meaning that
// the only allowed region constraints may be constraints also present on the default impl.
if lhs_evaluation.must_apply_modulo_regions() {
if tcx.specializes((lhs, victim)) {
return true;
}
}
match tcx.impls_are_allowed_to_overlap(lhs, victim) {
// For #33140 the impl headers must be exactly equal, the trait must not have
// any associated items and there are no where-clauses.
//
// We can just arbitrarily drop one of the impls.
Some(ty::ImplOverlapKind::FutureCompatOrderDepTraitObjects) => {
assert_eq!(lhs_evaluation, victim_evaluation);
true
}
// For candidates which already reference errors it doesn't really
// matter what we do 🤷
Some(ty::ImplOverlapKind::Permitted { marker: false }) => {
lhs_evaluation.must_apply_considering_regions()
}
Some(ty::ImplOverlapKind::Permitted { marker: true }) => {
// Subtle: If the predicate we are evaluating has inference
// variables, do *not* allow discarding candidates due to
// marker trait impls.
// //
// Global bounds from the where clause should be ignored // Without this restriction, we could end up accidentally
// here (see issue #50825). // constraining inference variables based on an arbitrarily
DropVictim::drop_if(!is_global(*other_cand)) // chosen trait impl.
}
(ObjectCandidate(_) | ProjectionCandidate(_), ParamCandidate(victim_cand)) => {
// Prefer these to a global where-clause bound
// (see issue #50825).
if is_global(*victim_cand) { DropVictim::Yes } else { DropVictim::No }
}
(
ImplCandidate(_)
| AutoImplCandidate
| ClosureCandidate { .. }
| AsyncClosureCandidate
| AsyncFnKindHelperCandidate
| CoroutineCandidate
| FutureCandidate
| IteratorCandidate
| AsyncIteratorCandidate
| FnPointerCandidate { .. }
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| TraitAliasCandidate,
ParamCandidate(victim_cand),
) => {
// Prefer these to a global where-clause bound
// (see issue #50825).
DropVictim::drop_if(
is_global(*victim_cand) && other.evaluation.must_apply_modulo_regions(),
)
}
(ProjectionCandidate(i), ProjectionCandidate(j))
| (ObjectCandidate(i), ObjectCandidate(j)) => {
// Arbitrarily pick the lower numbered candidate for backwards
// compatibility reasons. Don't let this affect inference.
DropVictim::drop_if(i < j && !has_non_region_infer)
}
(ObjectCandidate(_), ProjectionCandidate(_))
| (ProjectionCandidate(_), ObjectCandidate(_)) => {
bug!("Have both object and projection candidate")
}
// Arbitrarily give projection and object candidates priority.
(
ObjectCandidate(_) | ProjectionCandidate(_),
ImplCandidate(..)
| AutoImplCandidate
| ClosureCandidate { .. }
| AsyncClosureCandidate
| AsyncFnKindHelperCandidate
| CoroutineCandidate
| FutureCandidate
| IteratorCandidate
| AsyncIteratorCandidate
| FnPointerCandidate { .. }
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| TraitAliasCandidate,
) => DropVictim::Yes,
(
ImplCandidate(..)
| AutoImplCandidate
| ClosureCandidate { .. }
| AsyncClosureCandidate
| AsyncFnKindHelperCandidate
| CoroutineCandidate
| FutureCandidate
| IteratorCandidate
| AsyncIteratorCandidate
| FnPointerCandidate { .. }
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| TraitAliasCandidate,
ObjectCandidate(_) | ProjectionCandidate(_),
) => DropVictim::No,
(&ImplCandidate(other_def), &ImplCandidate(victim_def)) => {
// See if we can toss out `victim` based on specialization.
// While this requires us to know *for sure* that the `other` impl applies
// we still use modulo regions here.
// //
// This is fine as specialization currently assumes that specializing // Imagine we have the following code:
// impls have to be always applicable, meaning that the only allowed //
// region constraints may be constraints also present on the default impl. // ```rust
let tcx = self.tcx(); // #[marker] trait MyTrait {}
if other.evaluation.must_apply_modulo_regions() // impl MyTrait for u8 {}
&& tcx.specializes((other_def, victim_def)) // impl MyTrait for bool {}
{ // ```
return DropVictim::Yes; //
} // And we are evaluating the predicate `<_#0t as MyTrait>`.
//
match tcx.impls_are_allowed_to_overlap(other_def, victim_def) { // During selection, we will end up with one candidate for each
// For #33140 the impl headers must be exactly equal, the trait must not have // impl of `MyTrait`. If we were to discard one impl in favor
// any associated items and there are no where-clauses. // of the other, we would be left with one candidate, causing
// // us to "successfully" select the predicate, unifying
// We can just arbitrarily drop one of the impls. // _#0t with (for example) `u8`.
Some(ty::ImplOverlapKind::FutureCompatOrderDepTraitObjects) => { //
assert_eq!(other.evaluation, victim.evaluation); // However, we have no reason to believe that this unification
DropVictim::Yes // is correct - we've essentially just picked an arbitrary
} // *possibility* for _#0t, and required that this be the *only*
// For candidates which already reference errors it doesn't really // possibility.
// matter what we do 🤷 //
Some(ty::ImplOverlapKind::Permitted { marker: false }) => { // Eventually, we will either:
DropVictim::drop_if(other.evaluation.must_apply_considering_regions()) // 1) Unify all inference variables in the predicate through
} // some other means (e.g. type-checking of a function). We will
Some(ty::ImplOverlapKind::Permitted { marker: true }) => { // then be in a position to drop marker trait candidates
// Subtle: If the predicate we are evaluating has inference // without constraining inference variables (since there are
// variables, do *not* allow discarding candidates due to // none left to constrain)
// marker trait impls. // 2) Be left with some unconstrained inference variables. We
// // will then correctly report an inference error, since the
// Without this restriction, we could end up accidentally // existence of multiple marker trait impls tells us nothing
// constraining inference variables based on an arbitrarily // about which one should actually apply.
// chosen trait impl. !has_non_region_infer && lhs_evaluation.must_apply_considering_regions()
//
// Imagine we have the following code:
//
// ```rust
// #[marker] trait MyTrait {}
// impl MyTrait for u8 {}
// impl MyTrait for bool {}
// ```
//
// And we are evaluating the predicate `<_#0t as MyTrait>`.
//
// During selection, we will end up with one candidate for each
// impl of `MyTrait`. If we were to discard one impl in favor
// of the other, we would be left with one candidate, causing
// us to "successfully" select the predicate, unifying
// _#0t with (for example) `u8`.
//
// However, we have no reason to believe that this unification
// is correct - we've essentially just picked an arbitrary
// *possibility* for _#0t, and required that this be the *only*
// possibility.
//
// Eventually, we will either:
// 1) Unify all inference variables in the predicate through
// some other means (e.g. type-checking of a function). We will
// then be in a position to drop marker trait candidates
// without constraining inference variables (since there are
// none left to constrain)
// 2) Be left with some unconstrained inference variables. We
// will then correctly report an inference error, since the
// existence of multiple marker trait impls tells us nothing
// about which one should actually apply.
DropVictim::drop_if(
!has_non_region_infer
&& other.evaluation.must_apply_considering_regions(),
)
}
None => DropVictim::No,
}
} }
None => false,
(AutoImplCandidate, ImplCandidate(_)) | (ImplCandidate(_), AutoImplCandidate) => {
DropVictim::No
}
(AutoImplCandidate, _) | (_, AutoImplCandidate) => {
bug!(
"default implementations shouldn't be recorded \
when there are other global candidates: {:?} {:?}",
other,
victim
);
}
// Everything else is ambiguous
(
ImplCandidate(_)
| ClosureCandidate { .. }
| AsyncClosureCandidate
| AsyncFnKindHelperCandidate
| CoroutineCandidate
| FutureCandidate
| IteratorCandidate
| AsyncIteratorCandidate
| FnPointerCandidate { .. }
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| TraitAliasCandidate,
ImplCandidate(_)
| ClosureCandidate { .. }
| AsyncClosureCandidate
| AsyncFnKindHelperCandidate
| CoroutineCandidate
| FutureCandidate
| IteratorCandidate
| AsyncIteratorCandidate
| FnPointerCandidate { .. }
| BuiltinObjectCandidate
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| TraitAliasCandidate,
) => DropVictim::No,
} }
} }
} }

View file

@ -1,3 +1,5 @@
//@ check-pass
// A regression test for an edge case of candidate selection // A regression test for an edge case of candidate selection
// in the old trait solver, see #132325 for more details. Unlike // in the old trait solver, see #132325 for more details. Unlike
// the first test, this one has two impl candidates. // the first test, this one has two impl candidates.
@ -12,7 +14,7 @@ where
(): Trait<u32>, (): Trait<u32>,
(): Trait<T>, (): Trait<T>,
{ {
impls_trait(()) //~ ERROR mismatched types impls_trait(())
} }
fn main() {} fn main() {}

View file

@ -1,17 +0,0 @@
error[E0308]: mismatched types
--> $DIR/global-non-global-env-2.rs:15:5
|
LL | fn foo<T>() -> u32
| - --- expected `u32` because of return type
| |
| found this type parameter
...
LL | impls_trait(())
| ^^^^^^^^^^^^^^^ expected `u32`, found type parameter `T`
|
= note: expected type `u32`
found type parameter `T`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

View file

@ -1,3 +1,5 @@
//@ check-pass
// A regression test for an edge case of candidate selection // A regression test for an edge case of candidate selection
// in the old trait solver, see #132325 for more details. Unlike // in the old trait solver, see #132325 for more details. Unlike
// the third test, this one has 3 impl candidates. // the third test, this one has 3 impl candidates.
@ -13,7 +15,7 @@ where
(): Trait<T>, (): Trait<T>,
(): Trait<u32>, (): Trait<u32>,
{ {
impls_trait(()) //~ ERROR mismatched types impls_trait(())
} }
fn main() {} fn main() {}

View file

@ -1,17 +0,0 @@
error[E0308]: mismatched types
--> $DIR/global-non-global-env-4.rs:16:5
|
LL | fn foo<T>() -> u32
| - --- expected `u32` because of return type
| |
| found this type parameter
...
LL | impls_trait(())
| ^^^^^^^^^^^^^^^ expected `u32`, found type parameter `T`
|
= note: expected type `u32`
found type parameter `T`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.