1
Fork 0

Implement lint against coinductive impl overlap

This commit is contained in:
Michael Goulet 2023-07-25 01:49:29 +00:00
parent 2ae4bedd85
commit 56f5704ff8
5 changed files with 183 additions and 6 deletions

View file

@ -24,6 +24,7 @@ use rustc_middle::traits::DefiningAnchor;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor};
use rustc_session::lint::builtin::COINDUCTIVE_OVERLAP_IN_COHERENCE;
use rustc_span::symbol::sym;
use rustc_span::DUMMY_SP;
use std::fmt::Debug;
@ -295,9 +296,8 @@ fn impl_intersection_has_impossible_obligation<'cx, 'tcx>(
if infcx.next_trait_solver() {
infcx.evaluate_obligation(obligation).map_or(false, |result| !result.may_apply())
} else {
// We use `evaluate_root_obligation` to correctly track
// intercrate ambiguity clauses. We do not need this in the
// new solver.
// We use `evaluate_root_obligation` to correctly track intercrate
// ambiguity clauses. We cannot use this in the new solver.
selcx.evaluate_root_obligation(obligation).map_or(
false, // Overflow has occurred, and treat the obligation as possibly holding.
|result| !result.may_apply(),
@ -315,6 +315,45 @@ fn impl_intersection_has_impossible_obligation<'cx, 'tcx>(
.find(obligation_guaranteed_to_fail);
if let Some(failing_obligation) = opt_failing_obligation {
// Check the failing obligation once again, treating inductive cycles as
// ambiguous instead of error.
if !infcx.next_trait_solver()
&& SelectionContext::with_treat_inductive_cycle_as_ambiguous(infcx)
.evaluate_root_obligation(&failing_obligation)
.map_or(true, |result| result.may_apply())
{
let first_local_impl = impl1_header
.impl_def_id
.as_local()
.or(impl2_header.impl_def_id.as_local())
.expect("expected one of the impls to be local");
infcx.tcx.struct_span_lint_hir(
COINDUCTIVE_OVERLAP_IN_COHERENCE,
infcx.tcx.local_def_id_to_hir_id(first_local_impl),
infcx.tcx.def_span(first_local_impl),
"impls that are not considered to overlap may be considered to \
overlap in the future",
|lint| {
lint.span_label(
infcx.tcx.def_span(impl1_header.impl_def_id),
"the first impl is here",
)
.span_label(
infcx.tcx.def_span(impl2_header.impl_def_id),
"the second impl is here",
);
if !failing_obligation.cause.span.is_dummy() {
lint.span_label(
failing_obligation.cause.span,
"this where-clause causes a cycle, but it may be treated \
as possibly holding in the future, causing the impls to overlap",
);
}
lint
},
);
}
debug!("overlap: obligation unsatisfiable {:?}", failing_obligation);
true
} else {

View file

@ -118,6 +118,8 @@ pub struct SelectionContext<'cx, 'tcx> {
/// policy. In essence, canonicalized queries need their errors propagated
/// rather than immediately reported because we do not have accurate spans.
query_mode: TraitQueryMode,
treat_inductive_cycle: TreatInductiveCycleAs,
}
// A stack that walks back up the stack frame.
@ -198,6 +200,11 @@ enum BuiltinImplConditions<'tcx> {
Ambiguous,
}
enum TreatInductiveCycleAs {
Recur,
Ambig,
}
impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
pub fn new(infcx: &'cx InferCtxt<'tcx>) -> SelectionContext<'cx, 'tcx> {
SelectionContext {
@ -205,6 +212,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
freshener: infcx.freshener(),
intercrate_ambiguity_causes: None,
query_mode: TraitQueryMode::Standard,
treat_inductive_cycle: TreatInductiveCycleAs::Recur,
}
}
pub fn with_treat_inductive_cycle_as_ambiguous(
infcx: &'cx InferCtxt<'tcx>,
) -> SelectionContext<'cx, 'tcx> {
assert!(infcx.intercrate, "this doesn't do caching yet, so don't use it out of intercrate");
SelectionContext {
infcx,
freshener: infcx.freshener(),
intercrate_ambiguity_causes: None,
query_mode: TraitQueryMode::Standard,
treat_inductive_cycle: TreatInductiveCycleAs::Ambig,
}
}
@ -719,7 +740,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
stack.update_reached_depth(stack_arg.1);
return Ok(EvaluatedToOk);
} else {
return Ok(EvaluatedToRecur);
match self.treat_inductive_cycle {
TreatInductiveCycleAs::Ambig => return Ok(EvaluatedToAmbig),
TreatInductiveCycleAs::Recur => return Ok(EvaluatedToRecur),
}
}
}
return Ok(EvaluatedToOk);
@ -837,7 +861,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
}
ProjectAndUnifyResult::FailedNormalization => Ok(EvaluatedToAmbig),
ProjectAndUnifyResult::Recursive => Ok(EvaluatedToRecur),
ProjectAndUnifyResult::Recursive => match self.treat_inductive_cycle {
TreatInductiveCycleAs::Ambig => return Ok(EvaluatedToAmbig),
TreatInductiveCycleAs::Recur => return Ok(EvaluatedToRecur),
},
ProjectAndUnifyResult::MismatchedProjectionTypes(_) => Ok(EvaluatedToErr),
}
}
@ -1151,7 +1178,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
Some(EvaluatedToOk)
} else {
debug!("evaluate_stack --> recursive, inductive");
Some(EvaluatedToRecur)
match self.treat_inductive_cycle {
TreatInductiveCycleAs::Ambig => Some(EvaluatedToAmbig),
TreatInductiveCycleAs::Recur => Some(EvaluatedToRecur),
}
}
} else {
None