Skip to content

Commit 6cd6bad

Browse files
committed
Auto merge of #101692 - cjgillot:generator-lazy-witness, r=oli-obk
Compute generator saved locals on MIR Generators are currently type-checked by introducing a `witness` type variable, which is unified with a `GeneratorWitness(captured types)` whose purpose is to ensure that the auto traits correctly migrate from the captured types to the `witness` type. This requires computing the captured types on HIR during type-checking, only to re-do it on MIR later. This PR proposes to drop the HIR-based computation, and only keep the MIR one. This is done in 3 steps. 1. During type-checking, the `witness` type variable is never unified. This allows to stall all the obligations that depend on it until the end of type-checking. Then, the stalled obligations are marked as successful, and saved into the typeck results for later verification. 2. At type-checking writeback, `witness` is replaced by `GeneratorWitnessMIR(def_id, substs)`. From this point on, all trait selection involving `GeneratorWitnessMIR` will fetch the MIR-computed locals, similar to what opaque types do. There is no lifetime to be preserved here: we consider all the lifetimes appearing in this witness type to be higher-ranked. 3. After borrowck, the stashed obligations are verified against the actually computed types, in the `check_generator_obligations` query. If any obligation was wrongly marked as fulfilled in step 1, it should be reported here. There are still many issues: - ~I am not too happy having to filter out some locals from the checked bounds, I think this is MIR building that introduces raw pointers polluting the analysis;~ solved by a check specific to static variables. - the diagnostics for captured types don't show where they are used/dropped; - I do not attempt to support chalk. cc `@eholk` `@jyn514` for the drop-tracking work r? `@oli-obk` as you warned me of potential unsoundness
2 parents 7d4df2d + d3d6269 commit 6cd6bad

File tree

270 files changed

+6269
-601
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

270 files changed

+6269
-601
lines changed

compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs

+1
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ fn push_debuginfo_type_name<'tcx>(
414414
| ty::Placeholder(..)
415415
| ty::Alias(..)
416416
| ty::Bound(..)
417+
| ty::GeneratorWitnessMIR(..)
417418
| ty::GeneratorWitness(..) => {
418419
bug!(
419420
"debuginfo: Trying to create type name for \

compiler/rustc_const_eval/src/const_eval/valtrees.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
151151
// FIXME(oli-obk): we can probably encode closures just like structs
152152
| ty::Closure(..)
153153
| ty::Generator(..)
154-
| ty::GeneratorWitness(..) => Err(ValTreeCreationError::NonSupportedType),
154+
| ty::GeneratorWitness(..) |ty::GeneratorWitnessMIR(..)=> Err(ValTreeCreationError::NonSupportedType),
155155
}
156156
}
157157

@@ -314,6 +314,7 @@ pub fn valtree_to_const_value<'tcx>(
314314
| ty::Closure(..)
315315
| ty::Generator(..)
316316
| ty::GeneratorWitness(..)
317+
| ty::GeneratorWitnessMIR(..)
317318
| ty::FnPtr(_)
318319
| ty::RawPtr(_)
319320
| ty::Str

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
101101
| ty::Closure(_, _)
102102
| ty::Generator(_, _, _)
103103
| ty::GeneratorWitness(_)
104+
| ty::GeneratorWitnessMIR(_, _)
104105
| ty::Never
105106
| ty::Tuple(_)
106107
| ty::Error(_) => ConstValue::from_machine_usize(0u64, &tcx),

compiler/rustc_const_eval/src/interpret/validity.rs

+1
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
602602
| ty::Bound(..)
603603
| ty::Param(..)
604604
| ty::Alias(..)
605+
| ty::GeneratorWitnessMIR(..)
605606
| ty::GeneratorWitness(..) => bug!("Encountered invalid type {:?}", ty),
606607
}
607608
}

compiler/rustc_const_eval/src/transform/validate.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -372,12 +372,12 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
372372
return;
373373
};
374374

375-
let Some(&f_ty) = layout.field_tys.get(local) else {
375+
let Some(f_ty) = layout.field_tys.get(local) else {
376376
self.fail(location, format!("Out of bounds local {:?} for {:?}", local, parent_ty));
377377
return;
378378
};
379379

380-
f_ty
380+
f_ty.ty
381381
} else {
382382
let Some(f_ty) = substs.as_generator().prefix_tys().nth(f.index()) else {
383383
fail_out_of_bounds(self, location);

compiler/rustc_const_eval/src/util/type_name.rs

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
6464
ty::Foreign(def_id) => self.print_def_path(def_id, &[]),
6565

6666
ty::GeneratorWitness(_) => bug!("type_name: unexpected `GeneratorWitness`"),
67+
ty::GeneratorWitnessMIR(..) => bug!("type_name: unexpected `GeneratorWitnessMIR`"),
6768
}
6869
}
6970

compiler/rustc_hir/src/hir.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2106,8 +2106,8 @@ pub enum LocalSource {
21062106
}
21072107

21082108
/// Hints at the original code for a `match _ { .. }`.
2109-
#[derive(Copy, Clone, PartialEq, Eq, Encodable, Hash, Debug)]
2110-
#[derive(HashStable_Generic)]
2109+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
2110+
#[derive(HashStable_Generic, Encodable, Decodable)]
21112111
pub enum MatchSource {
21122112
/// A `match _ { .. }`.
21132113
Normal,

compiler/rustc_hir_analysis/src/check/check.rs

+35-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_hir::{ItemKind, Node, PathSegment};
1414
use rustc_infer::infer::opaque_types::ConstrainOpaqueTypeRegionVisitor;
1515
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
1616
use rustc_infer::infer::{DefiningAnchor, RegionVariableOrigin, TyCtxtInferExt};
17-
use rustc_infer::traits::Obligation;
17+
use rustc_infer::traits::{Obligation, TraitEngineExt as _};
1818
use rustc_lint::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS;
1919
use rustc_middle::hir::nested_filter;
2020
use rustc_middle::middle::stability::EvalResult;
@@ -28,7 +28,7 @@ use rustc_span::{self, Span};
2828
use rustc_target::spec::abi::Abi;
2929
use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplementedDirective;
3030
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
31-
use rustc_trait_selection::traits::{self, ObligationCtxt};
31+
use rustc_trait_selection::traits::{self, ObligationCtxt, TraitEngine, TraitEngineExt as _};
3232

3333
use std::ops::ControlFlow;
3434

@@ -1460,7 +1460,8 @@ fn opaque_type_cycle_error(
14601460
for def_id in visitor.opaques {
14611461
let ty_span = tcx.def_span(def_id);
14621462
if !seen.contains(&ty_span) {
1463-
err.span_label(ty_span, &format!("returning this opaque type `{ty}`"));
1463+
let descr = if ty.is_impl_trait() { "opaque " } else { "" };
1464+
err.span_label(ty_span, &format!("returning this {descr}type `{ty}`"));
14641465
seen.insert(ty_span);
14651466
}
14661467
err.span_label(sp, &format!("returning here with type `{ty}`"));
@@ -1507,3 +1508,34 @@ fn opaque_type_cycle_error(
15071508
}
15081509
err.emit()
15091510
}
1511+
1512+
pub(super) fn check_generator_obligations(tcx: TyCtxt<'_>, def_id: LocalDefId) {
1513+
debug_assert!(tcx.sess.opts.unstable_opts.drop_tracking_mir);
1514+
debug_assert!(matches!(tcx.def_kind(def_id), DefKind::Generator));
1515+
1516+
let typeck = tcx.typeck(def_id);
1517+
let param_env = tcx.param_env(def_id);
1518+
1519+
let generator_interior_predicates = &typeck.generator_interior_predicates[&def_id];
1520+
debug!(?generator_interior_predicates);
1521+
1522+
let infcx = tcx
1523+
.infer_ctxt()
1524+
// typeck writeback gives us predicates with their regions erased.
1525+
// As borrowck already has checked lifetimes, we do not need to do it again.
1526+
.ignoring_regions()
1527+
// Bind opaque types to `def_id` as they should have been checked by borrowck.
1528+
.with_opaque_type_inference(DefiningAnchor::Bind(def_id))
1529+
.build();
1530+
1531+
let mut fulfillment_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
1532+
for (predicate, cause) in generator_interior_predicates {
1533+
let obligation = Obligation::new(tcx, cause.clone(), param_env, *predicate);
1534+
fulfillment_cx.register_predicate_obligation(&infcx, obligation);
1535+
}
1536+
let errors = fulfillment_cx.select_all_or_error(&infcx);
1537+
debug!(?errors);
1538+
if !errors.is_empty() {
1539+
infcx.err_ctxt().report_fulfillment_errors(&errors, None);
1540+
}
1541+
}

compiler/rustc_hir_analysis/src/check/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub fn provide(providers: &mut Providers) {
105105
region_scope_tree,
106106
collect_return_position_impl_trait_in_trait_tys,
107107
compare_impl_const: compare_impl_item::compare_impl_const_raw,
108+
check_generator_obligations: check::check_generator_obligations,
108109
..*providers
109110
};
110111
}

compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ impl<'tcx> InherentCollect<'tcx> {
240240
| ty::Closure(..)
241241
| ty::Generator(..)
242242
| ty::GeneratorWitness(..)
243+
| ty::GeneratorWitnessMIR(..)
243244
| ty::Bound(..)
244245
| ty::Placeholder(_)
245246
| ty::Infer(_) => {

compiler/rustc_hir_analysis/src/variance/constraints.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,12 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> {
295295
// types, where we use Error as the Self type
296296
}
297297

298-
ty::Placeholder(..) | ty::GeneratorWitness(..) | ty::Bound(..) | ty::Infer(..) => {
299-
bug!(
300-
"unexpected type encountered in \
301-
variance inference: {}",
302-
ty
303-
);
298+
ty::Placeholder(..)
299+
| ty::GeneratorWitness(..)
300+
| ty::GeneratorWitnessMIR(..)
301+
| ty::Bound(..)
302+
| ty::Infer(..) => {
303+
bug!("unexpected type encountered in variance inference: {}", ty);
304304
}
305305
}
306306
}

compiler/rustc_hir_typeck/src/cast.rs

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
130130
| ty::Float(_)
131131
| ty::Array(..)
132132
| ty::GeneratorWitness(..)
133+
| ty::GeneratorWitnessMIR(..)
133134
| ty::RawPtr(_)
134135
| ty::Ref(..)
135136
| ty::FnDef(..)

compiler/rustc_hir_typeck/src/check.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub(super) fn check_fn<'a, 'tcx>(
130130
let gen_ty = if let (Some(_), Some(gen_kind)) = (can_be_generator, body.generator_kind) {
131131
let interior = fcx
132132
.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span });
133-
fcx.deferred_generator_interiors.borrow_mut().push((body.id(), interior, gen_kind));
133+
fcx.deferred_generator_interiors.borrow_mut().push((fn_id, body.id(), interior, gen_kind));
134134

135135
let (resume_ty, yield_ty) = fcx.resume_yield_tys.unwrap();
136136
Some(GeneratorTypes {

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+61-4
Original file line numberDiff line numberDiff line change
@@ -517,16 +517,73 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
517517
}
518518

519519
pub(in super::super) fn resolve_generator_interiors(&self, def_id: DefId) {
520+
if self.tcx.sess.opts.unstable_opts.drop_tracking_mir {
521+
self.save_generator_interior_predicates(def_id);
522+
return;
523+
}
524+
525+
self.select_obligations_where_possible(|_| {});
526+
520527
let mut generators = self.deferred_generator_interiors.borrow_mut();
521-
for (body_id, interior, kind) in generators.drain(..) {
522-
self.select_obligations_where_possible(|_| {});
528+
for (_, body_id, interior, kind) in generators.drain(..) {
523529
crate::generator_interior::resolve_interior(self, def_id, body_id, interior, kind);
530+
self.select_obligations_where_possible(|_| {});
531+
}
532+
}
533+
534+
/// Unify the inference variables corresponding to generator witnesses, and save all the
535+
/// predicates that were stalled on those inference variables.
536+
///
537+
/// This process allows to conservatively save all predicates that do depend on the generator
538+
/// interior types, for later processing by `check_generator_obligations`.
539+
///
540+
/// We must not attempt to select obligations after this method has run, or risk query cycle
541+
/// ICE.
542+
#[instrument(level = "debug", skip(self))]
543+
fn save_generator_interior_predicates(&self, def_id: DefId) {
544+
// Try selecting all obligations that are not blocked on inference variables.
545+
// Once we start unifying generator witnesses, trying to select obligations on them will
546+
// trigger query cycle ICEs, as doing so requires MIR.
547+
self.select_obligations_where_possible(|_| {});
548+
549+
let generators = std::mem::take(&mut *self.deferred_generator_interiors.borrow_mut());
550+
debug!(?generators);
551+
552+
for &(expr_hir_id, body_id, interior, _) in generators.iter() {
553+
let expr_def_id = self.tcx.hir().local_def_id(expr_hir_id);
554+
debug!(?expr_def_id);
555+
556+
// Create the `GeneratorWitness` type that we will unify with `interior`.
557+
let substs = ty::InternalSubsts::identity_for_item(
558+
self.tcx,
559+
self.tcx.typeck_root_def_id(expr_def_id.to_def_id()),
560+
);
561+
let witness = self.tcx.mk_generator_witness_mir(expr_def_id.to_def_id(), substs);
562+
563+
// Unify `interior` with `witness` and collect all the resulting obligations.
564+
let span = self.tcx.hir().body(body_id).value.span;
565+
let ok = self
566+
.at(&self.misc(span), self.param_env)
567+
.eq(interior, witness)
568+
.expect("Failed to unify generator interior type");
569+
let mut obligations = ok.obligations;
570+
571+
// Also collect the obligations that were unstalled by this unification.
572+
obligations
573+
.extend(self.fulfillment_cx.borrow_mut().drain_unstalled_obligations(&self.infcx));
574+
575+
let obligations = obligations.into_iter().map(|o| (o.predicate, o.cause)).collect();
576+
debug!(?obligations);
577+
self.typeck_results
578+
.borrow_mut()
579+
.generator_interior_predicates
580+
.insert(expr_def_id, obligations);
524581
}
525582
}
526583

527584
#[instrument(skip(self), level = "debug")]
528-
pub(in super::super) fn select_all_obligations_or_error(&self) {
529-
let mut errors = self.fulfillment_cx.borrow_mut().select_all_or_error(&self);
585+
pub(in super::super) fn report_ambiguity_errors(&self) {
586+
let mut errors = self.fulfillment_cx.borrow_mut().collect_remaining_errors();
530587

531588
if !errors.is_empty() {
532589
self.adjust_fulfillment_errors_for_expr_obligation(&mut errors);

compiler/rustc_hir_typeck/src/inherited.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub struct Inherited<'tcx> {
5656
pub(super) deferred_asm_checks: RefCell<Vec<(&'tcx hir::InlineAsm<'tcx>, hir::HirId)>>,
5757

5858
pub(super) deferred_generator_interiors:
59-
RefCell<Vec<(hir::BodyId, Ty<'tcx>, hir::GeneratorKind)>>,
59+
RefCell<Vec<(hir::HirId, hir::BodyId, Ty<'tcx>, hir::GeneratorKind)>>,
6060

6161
pub(super) body_id: Option<hir::BodyId>,
6262

compiler/rustc_hir_typeck/src/lib.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,24 @@ fn typeck_with_fallback<'tcx>(
294294
// Before the generator analysis, temporary scopes shall be marked to provide more
295295
// precise information on types to be captured.
296296
fcx.resolve_rvalue_scopes(def_id.to_def_id());
297-
fcx.resolve_generator_interiors(def_id.to_def_id());
298297

299298
for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) {
300299
let ty = fcx.normalize(span, ty);
301300
fcx.require_type_is_sized(ty, span, code);
302301
}
303302

304-
fcx.select_all_obligations_or_error();
303+
fcx.select_obligations_where_possible(|_| {});
304+
305+
debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations());
306+
307+
// This must be the last thing before `report_ambiguity_errors`.
308+
fcx.resolve_generator_interiors(def_id.to_def_id());
309+
310+
debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations());
311+
312+
if let None = fcx.infcx.tainted_by_errors() {
313+
fcx.report_ambiguity_errors();
314+
}
305315

306316
if let None = fcx.infcx.tainted_by_errors() {
307317
fcx.check_transmutes();

compiler/rustc_hir_typeck/src/writeback.rs

+4
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,10 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
545545
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
546546
self.typeck_results.generator_interior_types =
547547
fcx_typeck_results.generator_interior_types.clone();
548+
for (&expr_def_id, predicates) in fcx_typeck_results.generator_interior_predicates.iter() {
549+
let predicates = self.resolve(predicates.clone(), &self.fcx.tcx.def_span(expr_def_id));
550+
self.typeck_results.generator_interior_predicates.insert(expr_def_id, predicates);
551+
}
548552
}
549553

550554
#[instrument(skip(self), level = "debug")]

compiler/rustc_infer/src/infer/canonical/canonicalizer.rs

+1
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for Canonicalizer<'cx, 'tcx> {
435435
ty::Closure(..)
436436
| ty::Generator(..)
437437
| ty::GeneratorWitness(..)
438+
| ty::GeneratorWitnessMIR(..)
438439
| ty::Bool
439440
| ty::Char
440441
| ty::Int(..)

compiler/rustc_infer/src/infer/canonical/query_response.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::infer::region_constraints::{Constraint, RegionConstraintData};
1717
use crate::infer::{InferCtxt, InferOk, InferResult, NllRegionVariableOrigin};
1818
use crate::traits::query::{Fallible, NoSolution};
1919
use crate::traits::{Obligation, ObligationCause, PredicateObligation};
20-
use crate::traits::{PredicateObligations, TraitEngine};
20+
use crate::traits::{PredicateObligations, TraitEngine, TraitEngineExt};
2121
use rustc_data_structures::captures::Captures;
2222
use rustc_index::vec::Idx;
2323
use rustc_index::vec::IndexVec;

compiler/rustc_infer/src/infer/freshen.rs

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> {
209209
| ty::Foreign(..)
210210
| ty::Param(..)
211211
| ty::Closure(..)
212+
| ty::GeneratorWitnessMIR(..)
212213
| ty::GeneratorWitness(..) => t.super_fold_with(self),
213214

214215
ty::Placeholder(..) | ty::Bound(..) => bug!("unexpected type {:?}", t),

compiler/rustc_infer/src/infer/outlives/components.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ fn compute_components<'tcx>(
112112
}
113113

114114
// All regions are bound inside a witness
115-
ty::GeneratorWitness(..) => (),
115+
ty::GeneratorWitness(..) | ty::GeneratorWitnessMIR(..) => (),
116116

117117
// OutlivesTypeParameterEnv -- the actual checking that `X:'a`
118118
// is implied by the environment is done in regionck.

compiler/rustc_infer/src/traits/engine.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,19 @@ pub trait TraitEngine<'tcx>: 'tcx {
3636
obligation: PredicateObligation<'tcx>,
3737
);
3838

39-
fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>>;
40-
4139
fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>>;
4240

41+
fn collect_remaining_errors(&mut self) -> Vec<FulfillmentError<'tcx>>;
42+
4343
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>>;
44+
45+
/// Among all pending obligations, collect those are stalled on a inference variable which has
46+
/// changed since the last call to `select_where_possible`. Those obligations are marked as
47+
/// successful and returned.
48+
fn drain_unstalled_obligations(
49+
&mut self,
50+
infcx: &InferCtxt<'tcx>,
51+
) -> Vec<PredicateObligation<'tcx>>;
4452
}
4553

4654
pub trait TraitEngineExt<'tcx> {
@@ -49,6 +57,8 @@ pub trait TraitEngineExt<'tcx> {
4957
infcx: &InferCtxt<'tcx>,
5058
obligations: impl IntoIterator<Item = PredicateObligation<'tcx>>,
5159
);
60+
61+
fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>>;
5262
}
5363

5464
impl<'tcx, T: ?Sized + TraitEngine<'tcx>> TraitEngineExt<'tcx> for T {
@@ -61,4 +71,13 @@ impl<'tcx, T: ?Sized + TraitEngine<'tcx>> TraitEngineExt<'tcx> for T {
6171
self.register_predicate_obligation(infcx, obligation);
6272
}
6373
}
74+
75+
fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
76+
let errors = self.select_where_possible(infcx);
77+
if !errors.is_empty() {
78+
return errors;
79+
}
80+
81+
self.collect_remaining_errors()
82+
}
6483
}

0 commit comments

Comments
 (0)