introduce a dummy leak check and invoke it in all the right places
This set of diffs was produced by combing through
b68fad670b
and seeing where the
`leak_check` used to be invoked and how.
This commit is contained in:
parent
2cbe07b5b3
commit
0c94ea0bf1
7 changed files with 105 additions and 39 deletions
|
@ -4,6 +4,7 @@
|
||||||
use super::combine::CombineFields;
|
use super::combine::CombineFields;
|
||||||
use super::{HigherRankedType, InferCtxt, PlaceholderMap};
|
use super::{HigherRankedType, InferCtxt, PlaceholderMap};
|
||||||
|
|
||||||
|
use crate::infer::CombinedSnapshot;
|
||||||
use crate::ty::relate::{Relate, RelateResult, TypeRelation};
|
use crate::ty::relate::{Relate, RelateResult, TypeRelation};
|
||||||
use crate::ty::{self, Binder, TypeFoldable};
|
use crate::ty::{self, Binder, TypeFoldable};
|
||||||
|
|
||||||
|
@ -29,10 +30,10 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
|
||||||
|
|
||||||
let span = self.trace.cause.span;
|
let span = self.trace.cause.span;
|
||||||
|
|
||||||
return self.infcx.commit_if_ok(|_snapshot| {
|
return self.infcx.commit_if_ok(|snapshot| {
|
||||||
// First, we instantiate each bound region in the supertype with a
|
// First, we instantiate each bound region in the supertype with a
|
||||||
// fresh placeholder region.
|
// fresh placeholder region.
|
||||||
let (b_prime, _) = self.infcx.replace_bound_vars_with_placeholders(b);
|
let (b_prime, placeholder_map) = self.infcx.replace_bound_vars_with_placeholders(b);
|
||||||
|
|
||||||
// Next, we instantiate each bound region in the subtype
|
// Next, we instantiate each bound region in the subtype
|
||||||
// with a fresh region variable. These region variables --
|
// with a fresh region variable. These region variables --
|
||||||
|
@ -48,6 +49,9 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
|
||||||
// Compare types now that bound regions have been replaced.
|
// Compare types now that bound regions have been replaced.
|
||||||
let result = self.sub(a_is_expected).relate(&a_prime, &b_prime)?;
|
let result = self.sub(a_is_expected).relate(&a_prime, &b_prime)?;
|
||||||
|
|
||||||
|
self.infcx
|
||||||
|
.leak_check(!a_is_expected, &placeholder_map, snapshot)?;
|
||||||
|
|
||||||
debug!("higher_ranked_sub: OK result={:?}", result);
|
debug!("higher_ranked_sub: OK result={:?}", result);
|
||||||
|
|
||||||
Ok(ty::Binder::bind(result))
|
Ok(ty::Binder::bind(result))
|
||||||
|
@ -108,4 +112,22 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
|
||||||
|
|
||||||
(result, map)
|
(result, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Searches region constraints created since `snapshot` that
|
||||||
|
/// affect one of the placeholders in `placeholder_map`, returning
|
||||||
|
/// an error if any of the placeholders are related to another
|
||||||
|
/// placeholder or would have to escape into some parent universe
|
||||||
|
/// that cannot name them.
|
||||||
|
///
|
||||||
|
/// This is a temporary backwards compatibility measure to try and
|
||||||
|
/// retain the older (arguably incorrect) behavior of the
|
||||||
|
/// compiler.
|
||||||
|
pub fn leak_check(
|
||||||
|
&self,
|
||||||
|
_overly_polymorphic: bool,
|
||||||
|
_placeholder_map: &PlaceholderMap<'tcx>,
|
||||||
|
_snapshot: &CombinedSnapshot<'_, 'tcx>,
|
||||||
|
) -> RelateResult<'tcx, ()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -937,21 +937,22 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(self.commit_if_ok(|_snapshot| {
|
Some(self.commit_if_ok(|snapshot| {
|
||||||
let (
|
let (
|
||||||
ty::SubtypePredicate {
|
ty::SubtypePredicate {
|
||||||
a_is_expected,
|
a_is_expected,
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
},
|
},
|
||||||
_,
|
placeholder_map,
|
||||||
) = self.replace_bound_vars_with_placeholders(predicate);
|
) = self.replace_bound_vars_with_placeholders(predicate);
|
||||||
|
|
||||||
Ok(
|
let ok = self.at(cause, param_env)
|
||||||
self.at(cause, param_env)
|
.sub_exp(a_is_expected, a, b)?;
|
||||||
.sub_exp(a_is_expected, a, b)?
|
|
||||||
.unit(),
|
self.leak_check(false, &placeholder_map, snapshot)?;
|
||||||
)
|
|
||||||
|
Ok(ok.unit())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,12 +960,18 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
|
||||||
&self,
|
&self,
|
||||||
cause: &traits::ObligationCause<'tcx>,
|
cause: &traits::ObligationCause<'tcx>,
|
||||||
predicate: &ty::PolyRegionOutlivesPredicate<'tcx>,
|
predicate: &ty::PolyRegionOutlivesPredicate<'tcx>,
|
||||||
) {
|
) -> UnitResult<'tcx> {
|
||||||
let (ty::OutlivesPredicate(r_a, r_b), _) =
|
self.commit_if_ok(|snapshot| {
|
||||||
self.replace_bound_vars_with_placeholders(predicate);
|
let (ty::OutlivesPredicate(r_a, r_b), placeholder_map) =
|
||||||
let origin =
|
self.replace_bound_vars_with_placeholders(predicate);
|
||||||
SubregionOrigin::from_obligation_cause(cause, || RelateRegionParamBound(cause.span));
|
let origin = SubregionOrigin::from_obligation_cause(
|
||||||
self.sub_regions(origin, r_b, r_a); // `b : a` ==> `a <= b`
|
cause,
|
||||||
|
|| RelateRegionParamBound(cause.span),
|
||||||
|
);
|
||||||
|
self.sub_regions(origin, r_b, r_a); // `b : a` ==> `a <= b`
|
||||||
|
self.leak_check(false, &placeholder_map, snapshot)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_ty_var_id(&self, diverging: bool, origin: TypeVariableOrigin) -> TyVid {
|
pub fn next_ty_var_id(&self, diverging: bool, origin: TypeVariableOrigin) -> TyVid {
|
||||||
|
|
|
@ -771,7 +771,13 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&ty::Predicate::RegionOutlives(ref binder) => {
|
&ty::Predicate::RegionOutlives(ref binder) => {
|
||||||
let () = select.infcx().region_outlives_predicate(&dummy_cause, binder);
|
if select
|
||||||
|
.infcx()
|
||||||
|
.region_outlives_predicate(&dummy_cause, binder)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&ty::Predicate::TypeOutlives(ref binder) => {
|
&ty::Predicate::TypeOutlives(ref binder) => {
|
||||||
match (
|
match (
|
||||||
|
|
|
@ -730,9 +730,14 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Predicate::RegionOutlives(ref predicate) => {
|
ty::Predicate::RegionOutlives(ref predicate) => {
|
||||||
// These errors should show up as region
|
let predicate = self.resolve_type_vars_if_possible(predicate);
|
||||||
// inference failures.
|
let err = self.region_outlives_predicate(&obligation.cause,
|
||||||
panic!("region outlives {:?} failed", predicate);
|
&predicate).err().unwrap();
|
||||||
|
struct_span_err!(
|
||||||
|
self.tcx.sess, span, E0279,
|
||||||
|
"the requirement `{}` is not satisfied (`{}`)",
|
||||||
|
predicate, err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Predicate::Projection(..) | ty::Predicate::TypeOutlives(..) => {
|
ty::Predicate::Projection(..) | ty::Predicate::TypeOutlives(..) => {
|
||||||
|
|
|
@ -331,8 +331,10 @@ impl<'a, 'b, 'gcx, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'gcx,
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Predicate::RegionOutlives(ref binder) => {
|
ty::Predicate::RegionOutlives(ref binder) => {
|
||||||
let () = self.selcx.infcx().region_outlives_predicate(&obligation.cause, binder);
|
match self.selcx.infcx().region_outlives_predicate(&obligation.cause, binder) {
|
||||||
ProcessResult::Changed(vec![])
|
Ok(()) => ProcessResult::Changed(vec![]),
|
||||||
|
Err(_) => ProcessResult::Error(CodeSelectionError(Unimplemented)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Predicate::TypeOutlives(ref binder) => {
|
ty::Predicate::TypeOutlives(ref binder) => {
|
||||||
|
|
|
@ -191,12 +191,15 @@ pub fn poly_project_and_unify_type<'cx, 'gcx, 'tcx>(
|
||||||
obligation);
|
obligation);
|
||||||
|
|
||||||
let infcx = selcx.infcx();
|
let infcx = selcx.infcx();
|
||||||
infcx.commit_if_ok(|_| {
|
infcx.commit_if_ok(|snapshot| {
|
||||||
let (placeholder_predicate, _) =
|
let (placeholder_predicate, placeholder_map) =
|
||||||
infcx.replace_bound_vars_with_placeholders(&obligation.predicate);
|
infcx.replace_bound_vars_with_placeholders(&obligation.predicate);
|
||||||
|
|
||||||
let placeholder_obligation = obligation.with(placeholder_predicate);
|
let placeholder_obligation = obligation.with(placeholder_predicate);
|
||||||
project_and_unify_type(selcx, &placeholder_obligation)
|
let result = project_and_unify_type(selcx, &placeholder_obligation)?;
|
||||||
|
infcx.leak_check(false, &placeholder_map, snapshot)
|
||||||
|
.map_err(|err| MismatchedProjectionTypes { err })?;
|
||||||
|
Ok(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1427,9 +1430,8 @@ fn confirm_callable_candidate<'cx, 'gcx, 'tcx>(
|
||||||
fn confirm_param_env_candidate<'cx, 'gcx, 'tcx>(
|
fn confirm_param_env_candidate<'cx, 'gcx, 'tcx>(
|
||||||
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
|
||||||
obligation: &ProjectionTyObligation<'tcx>,
|
obligation: &ProjectionTyObligation<'tcx>,
|
||||||
poly_cache_entry: ty::PolyProjectionPredicate<'tcx>)
|
poly_cache_entry: ty::PolyProjectionPredicate<'tcx>,
|
||||||
-> Progress<'tcx>
|
) -> Progress<'tcx> {
|
||||||
{
|
|
||||||
let infcx = selcx.infcx();
|
let infcx = selcx.infcx();
|
||||||
let cause = &obligation.cause;
|
let cause = &obligation.cause;
|
||||||
let param_env = obligation.param_env;
|
let param_env = obligation.param_env;
|
||||||
|
|
|
@ -29,7 +29,7 @@ use super::{
|
||||||
|
|
||||||
use crate::dep_graph::{DepKind, DepNodeIndex};
|
use crate::dep_graph::{DepKind, DepNodeIndex};
|
||||||
use crate::hir::def_id::DefId;
|
use crate::hir::def_id::DefId;
|
||||||
use crate::infer::{InferCtxt, InferOk, TypeFreshener};
|
use crate::infer::{CombinedSnapshot, InferCtxt, InferOk, PlaceholderMap, TypeFreshener};
|
||||||
use crate::middle::lang_items;
|
use crate::middle::lang_items;
|
||||||
use crate::mir::interpret::GlobalId;
|
use crate::mir::interpret::GlobalId;
|
||||||
use crate::ty::fast_reject;
|
use crate::ty::fast_reject;
|
||||||
|
@ -1667,8 +1667,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = self.infcx.probe(|_| {
|
let result = self.infcx.probe(|snapshot| {
|
||||||
self.match_projection_obligation_against_definition_bounds(obligation)
|
self.match_projection_obligation_against_definition_bounds(
|
||||||
|
obligation,
|
||||||
|
snapshot,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if result {
|
if result {
|
||||||
|
@ -1679,10 +1682,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
fn match_projection_obligation_against_definition_bounds(
|
fn match_projection_obligation_against_definition_bounds(
|
||||||
&mut self,
|
&mut self,
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
|
snapshot: &CombinedSnapshot<'_, 'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let poly_trait_predicate = self.infcx()
|
let poly_trait_predicate = self.infcx()
|
||||||
.resolve_type_vars_if_possible(&obligation.predicate);
|
.resolve_type_vars_if_possible(&obligation.predicate);
|
||||||
let (placeholder_trait_predicate, _) = self.infcx()
|
let (placeholder_trait_predicate, placeholder_map) = self.infcx()
|
||||||
.replace_bound_vars_with_placeholders(&poly_trait_predicate);
|
.replace_bound_vars_with_placeholders(&poly_trait_predicate);
|
||||||
debug!(
|
debug!(
|
||||||
"match_projection_obligation_against_definition_bounds: \
|
"match_projection_obligation_against_definition_bounds: \
|
||||||
|
@ -1724,6 +1728,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
obligation,
|
obligation,
|
||||||
bound.clone(),
|
bound.clone(),
|
||||||
placeholder_trait_predicate.trait_ref.clone(),
|
placeholder_trait_predicate.trait_ref.clone(),
|
||||||
|
&placeholder_map,
|
||||||
|
snapshot,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -1741,6 +1747,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
obligation,
|
obligation,
|
||||||
bound,
|
bound,
|
||||||
placeholder_trait_predicate.trait_ref.clone(),
|
placeholder_trait_predicate.trait_ref.clone(),
|
||||||
|
&placeholder_map,
|
||||||
|
snapshot,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result);
|
assert!(result);
|
||||||
|
@ -1754,12 +1762,16 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
trait_bound: ty::PolyTraitRef<'tcx>,
|
trait_bound: ty::PolyTraitRef<'tcx>,
|
||||||
placeholder_trait_ref: ty::TraitRef<'tcx>,
|
placeholder_trait_ref: ty::TraitRef<'tcx>,
|
||||||
|
placeholder_map: &PlaceholderMap<'tcx>,
|
||||||
|
snapshot: &CombinedSnapshot<'_, 'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
debug_assert!(!placeholder_trait_ref.has_escaping_bound_vars());
|
debug_assert!(!placeholder_trait_ref.has_escaping_bound_vars());
|
||||||
self.infcx
|
self.infcx
|
||||||
.at(&obligation.cause, obligation.param_env)
|
.at(&obligation.cause, obligation.param_env)
|
||||||
.sup(ty::Binder::dummy(placeholder_trait_ref), trait_bound)
|
.sup(ty::Binder::dummy(placeholder_trait_ref), trait_bound)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|
&&
|
||||||
|
self.infcx.leak_check(false, placeholder_map, snapshot).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given an obligation like `<SomeTrait for T>`, search the obligations that the caller
|
/// Given an obligation like `<SomeTrait for T>`, search the obligations that the caller
|
||||||
|
@ -1960,8 +1972,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
obligation.predicate.def_id(),
|
obligation.predicate.def_id(),
|
||||||
obligation.predicate.skip_binder().trait_ref.self_ty(),
|
obligation.predicate.skip_binder().trait_ref.self_ty(),
|
||||||
|impl_def_id| {
|
|impl_def_id| {
|
||||||
self.infcx.probe(|_| {
|
self.infcx.probe(|snapshot| {
|
||||||
if let Ok(_substs) = self.match_impl(impl_def_id, obligation)
|
if let Ok(_substs) = self.match_impl(impl_def_id, obligation, snapshot)
|
||||||
{
|
{
|
||||||
candidates.vec.push(ImplCandidate(impl_def_id));
|
candidates.vec.push(ImplCandidate(impl_def_id));
|
||||||
}
|
}
|
||||||
|
@ -2758,9 +2770,12 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_projection_candidate(&mut self, obligation: &TraitObligation<'tcx>) {
|
fn confirm_projection_candidate(&mut self, obligation: &TraitObligation<'tcx>) {
|
||||||
self.infcx.in_snapshot(|_| {
|
self.infcx.in_snapshot(|snapshot| {
|
||||||
let result =
|
let result =
|
||||||
self.match_projection_obligation_against_definition_bounds(obligation);
|
self.match_projection_obligation_against_definition_bounds(
|
||||||
|
obligation,
|
||||||
|
snapshot,
|
||||||
|
);
|
||||||
assert!(result);
|
assert!(result);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2912,8 +2927,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
|
|
||||||
// First, create the substitutions by matching the impl again,
|
// First, create the substitutions by matching the impl again,
|
||||||
// this time not in a probe.
|
// this time not in a probe.
|
||||||
self.infcx.in_snapshot(|_| {
|
self.infcx.in_snapshot(|snapshot| {
|
||||||
let substs = self.rematch_impl(impl_def_id, obligation);
|
let substs = self.rematch_impl(impl_def_id, obligation, snapshot);
|
||||||
debug!("confirm_impl_candidate: substs={:?}", substs);
|
debug!("confirm_impl_candidate: substs={:?}", substs);
|
||||||
let cause = obligation.derived_cause(ImplDerivedObligation);
|
let cause = obligation.derived_cause(ImplDerivedObligation);
|
||||||
self.vtable_impl(
|
self.vtable_impl(
|
||||||
|
@ -3504,8 +3519,9 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
&mut self,
|
&mut self,
|
||||||
impl_def_id: DefId,
|
impl_def_id: DefId,
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
|
snapshot: &CombinedSnapshot<'_, 'tcx>,
|
||||||
) -> Normalized<'tcx, &'tcx Substs<'tcx>> {
|
) -> Normalized<'tcx, &'tcx Substs<'tcx>> {
|
||||||
match self.match_impl(impl_def_id, obligation) {
|
match self.match_impl(impl_def_id, obligation, snapshot) {
|
||||||
Ok(substs) => substs,
|
Ok(substs) => substs,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
bug!(
|
bug!(
|
||||||
|
@ -3521,6 +3537,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
&mut self,
|
&mut self,
|
||||||
impl_def_id: DefId,
|
impl_def_id: DefId,
|
||||||
obligation: &TraitObligation<'tcx>,
|
obligation: &TraitObligation<'tcx>,
|
||||||
|
snapshot: &CombinedSnapshot<'_, 'tcx>,
|
||||||
) -> Result<Normalized<'tcx, &'tcx Substs<'tcx>>, ()> {
|
) -> Result<Normalized<'tcx, &'tcx Substs<'tcx>>, ()> {
|
||||||
let impl_trait_ref = self.tcx().impl_trait_ref(impl_def_id).unwrap();
|
let impl_trait_ref = self.tcx().impl_trait_ref(impl_def_id).unwrap();
|
||||||
|
|
||||||
|
@ -3531,7 +3548,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (skol_obligation, _) = self.infcx()
|
let (skol_obligation, placeholder_map) = self.infcx()
|
||||||
.replace_bound_vars_with_placeholders(&obligation.predicate);
|
.replace_bound_vars_with_placeholders(&obligation.predicate);
|
||||||
let skol_obligation_trait_ref = skol_obligation.trait_ref;
|
let skol_obligation_trait_ref = skol_obligation.trait_ref;
|
||||||
|
|
||||||
|
@ -3563,6 +3580,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||||
.map_err(|e| debug!("match_impl: failed eq_trait_refs due to `{}`", e))?;
|
.map_err(|e| debug!("match_impl: failed eq_trait_refs due to `{}`", e))?;
|
||||||
nested_obligations.extend(obligations);
|
nested_obligations.extend(obligations);
|
||||||
|
|
||||||
|
if let Err(e) = self.infcx.leak_check(false, &placeholder_map, snapshot) {
|
||||||
|
debug!("match_impl: failed leak check due to `{}`", e);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
debug!("match_impl: success impl_substs={:?}", impl_substs);
|
debug!("match_impl: success impl_substs={:?}", impl_substs);
|
||||||
Ok(Normalized {
|
Ok(Normalized {
|
||||||
value: impl_substs,
|
value: impl_substs,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue