add TypingMode::Borrowck
This commit is contained in:
parent
990201cb78
commit
509a144eed
131 changed files with 888 additions and 747 deletions
|
@ -61,6 +61,7 @@ pub(crate) fn provide(providers: &mut Providers) {
|
|||
*providers = Providers {
|
||||
type_of: type_of::type_of,
|
||||
type_of_opaque: type_of::type_of_opaque,
|
||||
type_of_opaque_hir_typeck: type_of::type_of_opaque_hir_typeck,
|
||||
type_alias_is_lazy: type_of::type_alias_is_lazy,
|
||||
item_bounds: item_bounds::item_bounds,
|
||||
explicit_item_bounds: item_bounds::explicit_item_bounds,
|
||||
|
|
|
@ -7,7 +7,9 @@ use rustc_hir::{self as hir, AmbigArg, HirId};
|
|||
use rustc_middle::query::plumbing::CyclePlaceholder;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
use rustc_middle::ty::util::IntTypeExt;
|
||||
use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, fold_regions};
|
||||
use rustc_middle::ty::{
|
||||
self, DefiningScopeKind, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, fold_regions,
|
||||
};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::{DUMMY_SP, Ident, Span};
|
||||
|
||||
|
@ -324,10 +326,18 @@ pub(super) fn type_of_opaque(
|
|||
if let Some(def_id) = def_id.as_local() {
|
||||
Ok(ty::EarlyBinder::bind(match tcx.hir_node_by_def_id(def_id).expect_opaque_ty().origin {
|
||||
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false, .. } => {
|
||||
opaque::find_opaque_ty_constraints_for_tait(tcx, def_id)
|
||||
opaque::find_opaque_ty_constraints_for_tait(
|
||||
tcx,
|
||||
def_id,
|
||||
DefiningScopeKind::MirBorrowck,
|
||||
)
|
||||
}
|
||||
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: true, .. } => {
|
||||
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(tcx, def_id)
|
||||
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
||||
tcx,
|
||||
def_id,
|
||||
DefiningScopeKind::MirBorrowck,
|
||||
)
|
||||
}
|
||||
// Opaque types desugared from `impl Trait`.
|
||||
hir::OpaqueTyOrigin::FnReturn { parent: owner, in_trait_or_impl }
|
||||
|
@ -340,7 +350,12 @@ pub(super) fn type_of_opaque(
|
|||
"tried to get type of this RPITIT with no definition"
|
||||
);
|
||||
}
|
||||
opaque::find_opaque_ty_constraints_for_rpit(tcx, def_id, owner)
|
||||
opaque::find_opaque_ty_constraints_for_rpit(
|
||||
tcx,
|
||||
def_id,
|
||||
owner,
|
||||
DefiningScopeKind::MirBorrowck,
|
||||
)
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
|
@ -350,6 +365,42 @@ pub(super) fn type_of_opaque(
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn type_of_opaque_hir_typeck(
|
||||
tcx: TyCtxt<'_>,
|
||||
def_id: LocalDefId,
|
||||
) -> ty::EarlyBinder<'_, Ty<'_>> {
|
||||
ty::EarlyBinder::bind(match tcx.hir_node_by_def_id(def_id).expect_opaque_ty().origin {
|
||||
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false, .. } => {
|
||||
opaque::find_opaque_ty_constraints_for_tait(tcx, def_id, DefiningScopeKind::HirTypeck)
|
||||
}
|
||||
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: true, .. } => {
|
||||
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
||||
tcx,
|
||||
def_id,
|
||||
DefiningScopeKind::HirTypeck,
|
||||
)
|
||||
}
|
||||
// Opaque types desugared from `impl Trait`.
|
||||
hir::OpaqueTyOrigin::FnReturn { parent: owner, in_trait_or_impl }
|
||||
| hir::OpaqueTyOrigin::AsyncFn { parent: owner, in_trait_or_impl } => {
|
||||
if in_trait_or_impl == Some(hir::RpitContext::Trait)
|
||||
&& !tcx.defaultness(owner).has_value()
|
||||
{
|
||||
span_bug!(
|
||||
tcx.def_span(def_id),
|
||||
"tried to get type of this RPITIT with no definition"
|
||||
);
|
||||
}
|
||||
opaque::find_opaque_ty_constraints_for_rpit(
|
||||
tcx,
|
||||
def_id,
|
||||
owner,
|
||||
DefiningScopeKind::HirTypeck,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_placeholder_type<'tcx>(
|
||||
cx: &dyn HirTyLowerer<'tcx>,
|
||||
def_id: LocalDefId,
|
||||
|
|
|
@ -3,8 +3,7 @@ use rustc_hir::def_id::LocalDefId;
|
|||
use rustc_hir::{self as hir, Expr, ImplItem, Item, Node, TraitItem, def, intravisit};
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_middle::ty::{self, DefiningScopeKind, Ty, TyCtxt, TypeVisitableExt};
|
||||
use tracing::{debug, instrument, trace};
|
||||
|
||||
use crate::errors::{TaitForwardCompat2, UnconstrainedOpaqueType};
|
||||
|
@ -15,6 +14,7 @@ use crate::errors::{TaitForwardCompat2, UnconstrainedOpaqueType};
|
|||
pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
||||
tcx: TyCtxt<'_>,
|
||||
def_id: LocalDefId,
|
||||
opaque_types_from: DefiningScopeKind,
|
||||
) -> Ty<'_> {
|
||||
let mut parent_def_id = def_id;
|
||||
while tcx.def_kind(parent_def_id) == def::DefKind::OpaqueTy {
|
||||
|
@ -27,7 +27,7 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
|||
other => bug!("invalid impl trait in assoc type parent: {other:?}"),
|
||||
}
|
||||
|
||||
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] };
|
||||
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, opaque_types_from };
|
||||
|
||||
for &assoc_id in tcx.associated_item_def_ids(impl_def_id) {
|
||||
let assoc = tcx.associated_item(assoc_id);
|
||||
|
@ -39,25 +39,14 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
|||
}
|
||||
|
||||
if let Some(hidden) = locator.found {
|
||||
// Only check against typeck if we didn't already error
|
||||
if !hidden.ty.references_error() {
|
||||
for concrete_type in locator.typeck_types {
|
||||
if concrete_type.ty != tcx.erase_regions(hidden.ty) {
|
||||
if let Ok(d) = hidden.build_mismatch_error(&concrete_type, tcx) {
|
||||
d.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden.ty
|
||||
} else {
|
||||
let reported = tcx.dcx().emit_err(UnconstrainedOpaqueType {
|
||||
let guar = tcx.dcx().emit_err(UnconstrainedOpaqueType {
|
||||
span: tcx.def_span(def_id),
|
||||
name: tcx.item_ident(parent_def_id.to_def_id()),
|
||||
what: "impl",
|
||||
});
|
||||
Ty::new_error(tcx, reported)
|
||||
Ty::new_error(tcx, guar)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,23 +69,16 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
|
|||
/// fn b<T>() -> Foo<T, u32> { .. }
|
||||
/// ```
|
||||
#[instrument(skip(tcx), level = "debug")]
|
||||
pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Ty<'_> {
|
||||
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] };
|
||||
pub(super) fn find_opaque_ty_constraints_for_tait(
|
||||
tcx: TyCtxt<'_>,
|
||||
def_id: LocalDefId,
|
||||
opaque_types_from: DefiningScopeKind,
|
||||
) -> Ty<'_> {
|
||||
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, opaque_types_from };
|
||||
|
||||
tcx.hir_walk_toplevel_module(&mut locator);
|
||||
|
||||
if let Some(hidden) = locator.found {
|
||||
// Only check against typeck if we didn't already error
|
||||
if !hidden.ty.references_error() {
|
||||
for concrete_type in locator.typeck_types {
|
||||
if concrete_type.ty != tcx.erase_regions(hidden.ty) {
|
||||
if let Ok(d) = hidden.build_mismatch_error(&concrete_type, tcx) {
|
||||
d.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden.ty
|
||||
} else {
|
||||
let mut parent_def_id = def_id;
|
||||
|
@ -104,12 +86,12 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local
|
|||
// Account for `type Alias = impl Trait<Foo = impl Trait>;` (#116031)
|
||||
parent_def_id = tcx.local_parent(parent_def_id);
|
||||
}
|
||||
let reported = tcx.dcx().emit_err(UnconstrainedOpaqueType {
|
||||
let guar = tcx.dcx().emit_err(UnconstrainedOpaqueType {
|
||||
span: tcx.def_span(def_id),
|
||||
name: tcx.item_ident(parent_def_id.to_def_id()),
|
||||
what: "crate",
|
||||
});
|
||||
Ty::new_error(tcx, reported)
|
||||
Ty::new_error(tcx, guar)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,22 +108,44 @@ struct TaitConstraintLocator<'tcx> {
|
|||
/// type).
|
||||
found: Option<ty::OpaqueHiddenType<'tcx>>,
|
||||
|
||||
/// In the presence of dead code, typeck may figure out a hidden type
|
||||
/// while borrowck will not. We collect these cases here and check at
|
||||
/// the end that we actually found a type that matches (modulo regions).
|
||||
typeck_types: Vec<ty::OpaqueHiddenType<'tcx>>,
|
||||
opaque_types_from: DefiningScopeKind,
|
||||
}
|
||||
|
||||
impl TaitConstraintLocator<'_> {
|
||||
impl<'tcx> TaitConstraintLocator<'tcx> {
|
||||
fn insert_found(&mut self, hidden_ty: ty::OpaqueHiddenType<'tcx>) {
|
||||
if let Some(prev) = &mut self.found {
|
||||
if hidden_ty.ty != prev.ty {
|
||||
let (Ok(guar) | Err(guar)) =
|
||||
prev.build_mismatch_error(&hidden_ty, self.tcx).map(|d| d.emit());
|
||||
prev.ty = Ty::new_error(self.tcx, guar);
|
||||
}
|
||||
} else {
|
||||
self.found = Some(hidden_ty);
|
||||
}
|
||||
}
|
||||
|
||||
fn non_defining_use_in_defining_scope(&mut self, item_def_id: LocalDefId) {
|
||||
let guar = self.tcx.dcx().emit_err(TaitForwardCompat2 {
|
||||
span: self
|
||||
.tcx
|
||||
.def_ident_span(item_def_id)
|
||||
.unwrap_or_else(|| self.tcx.def_span(item_def_id)),
|
||||
opaque_type_span: self.tcx.def_span(self.def_id),
|
||||
opaque_type: self.tcx.def_path_str(self.def_id),
|
||||
});
|
||||
self.insert_found(ty::OpaqueHiddenType::new_error(self.tcx, guar));
|
||||
}
|
||||
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
fn check(&mut self, item_def_id: LocalDefId) {
|
||||
// Don't try to check items that cannot possibly constrain the type.
|
||||
if !self.tcx.has_typeck_results(item_def_id) {
|
||||
let tcx = self.tcx;
|
||||
if !tcx.has_typeck_results(item_def_id) {
|
||||
debug!("no constraint: no typeck results");
|
||||
return;
|
||||
}
|
||||
|
||||
let opaque_types_defined_by = self.tcx.opaque_types_defined_by(item_def_id);
|
||||
let opaque_types_defined_by = tcx.opaque_types_defined_by(item_def_id);
|
||||
// Don't try to check items that cannot possibly constrain the type.
|
||||
if !opaque_types_defined_by.contains(&self.def_id) {
|
||||
debug!("no constraint: no opaque types defined");
|
||||
|
@ -152,7 +156,7 @@ impl TaitConstraintLocator<'_> {
|
|||
// "non-defining use" errors for them.
|
||||
// Note that we use `Node::fn_sig` instead of `Node::fn_decl` here, because the former
|
||||
// excludes closures, which are allowed to have `_` in their return type.
|
||||
let hir_node = self.tcx.hir_node_by_def_id(item_def_id);
|
||||
let hir_node = tcx.hir_node_by_def_id(item_def_id);
|
||||
debug_assert!(
|
||||
!matches!(hir_node, Node::ForeignItem(..)),
|
||||
"foreign items cannot constrain opaque types",
|
||||
|
@ -164,88 +168,39 @@ impl TaitConstraintLocator<'_> {
|
|||
hir_sig.decl.output.span(),
|
||||
"inferring return types and opaque types do not mix well",
|
||||
);
|
||||
self.found =
|
||||
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
|
||||
self.found = Some(ty::OpaqueHiddenType::new_error(tcx, guar));
|
||||
return;
|
||||
}
|
||||
|
||||
// Calling `mir_borrowck` can lead to cycle errors through
|
||||
// const-checking, avoid calling it if we don't have to.
|
||||
// ```rust
|
||||
// type Foo = impl Fn() -> usize; // when computing type for this
|
||||
// const fn bar() -> Foo {
|
||||
// || 0usize
|
||||
// }
|
||||
// const BAZR: Foo = bar(); // we would mir-borrowck this, causing cycles
|
||||
// // because we again need to reveal `Foo` so we can check whether the
|
||||
// // constant does not contain interior mutability.
|
||||
// ```
|
||||
let tables = self.tcx.typeck(item_def_id);
|
||||
if let Some(guar) = tables.tainted_by_errors {
|
||||
self.found =
|
||||
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
|
||||
return;
|
||||
}
|
||||
|
||||
let mut constrained = false;
|
||||
for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
|
||||
if opaque_type_key.def_id != self.def_id {
|
||||
continue;
|
||||
}
|
||||
constrained = true;
|
||||
|
||||
let concrete_type =
|
||||
self.tcx.erase_regions(hidden_type.remap_generic_params_to_declaration_params(
|
||||
opaque_type_key,
|
||||
self.tcx,
|
||||
true,
|
||||
));
|
||||
if self.typeck_types.iter().all(|prev| prev.ty != concrete_type.ty) {
|
||||
self.typeck_types.push(concrete_type);
|
||||
}
|
||||
}
|
||||
|
||||
if !constrained {
|
||||
debug!("no constraints in typeck results");
|
||||
if opaque_types_defined_by.contains(&self.def_id) {
|
||||
let guar = self.tcx.dcx().emit_err(TaitForwardCompat2 {
|
||||
span: self
|
||||
.tcx
|
||||
.def_ident_span(item_def_id)
|
||||
.unwrap_or_else(|| self.tcx.def_span(item_def_id)),
|
||||
opaque_type_span: self.tcx.def_span(self.def_id),
|
||||
opaque_type: self.tcx.def_path_str(self.def_id),
|
||||
});
|
||||
// Avoid "opaque type not constrained" errors on the opaque itself.
|
||||
self.found = Some(ty::OpaqueHiddenType {
|
||||
span: DUMMY_SP,
|
||||
ty: Ty::new_error(self.tcx, guar),
|
||||
});
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// Use borrowck to get the type with unerased regions.
|
||||
let borrowck_results = &self.tcx.mir_borrowck(item_def_id);
|
||||
|
||||
// If the body was tainted, then assume the opaque may have been constrained and just set it to error.
|
||||
if let Some(guar) = borrowck_results.tainted_by_errors {
|
||||
self.found =
|
||||
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
|
||||
return;
|
||||
}
|
||||
|
||||
debug!(?borrowck_results.concrete_opaque_types);
|
||||
if let Some(&concrete_type) = borrowck_results.concrete_opaque_types.get(&self.def_id) {
|
||||
debug!(?concrete_type, "found constraint");
|
||||
if let Some(prev) = &mut self.found {
|
||||
if concrete_type.ty != prev.ty {
|
||||
let (Ok(guar) | Err(guar)) =
|
||||
prev.build_mismatch_error(&concrete_type, self.tcx).map(|d| d.emit());
|
||||
prev.ty = Ty::new_error(self.tcx, guar);
|
||||
match self.opaque_types_from {
|
||||
DefiningScopeKind::HirTypeck => {
|
||||
let tables = tcx.typeck(item_def_id);
|
||||
if let Some(guar) = tables.tainted_by_errors {
|
||||
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
|
||||
} else if let Some(&hidden_type) = tables.concrete_opaque_types.get(&self.def_id) {
|
||||
self.insert_found(hidden_type);
|
||||
} else {
|
||||
self.non_defining_use_in_defining_scope(item_def_id);
|
||||
}
|
||||
}
|
||||
DefiningScopeKind::MirBorrowck => {
|
||||
let borrowck_result = tcx.mir_borrowck(item_def_id);
|
||||
if let Some(guar) = borrowck_result.tainted_by_errors {
|
||||
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
|
||||
} else if let Some(&hidden_type) =
|
||||
borrowck_result.concrete_opaque_types.get(&self.def_id)
|
||||
{
|
||||
debug!(?hidden_type, "found constraint");
|
||||
self.insert_found(hidden_type);
|
||||
} else if let Err(guar) = tcx
|
||||
.type_of_opaque_hir_typeck(self.def_id)
|
||||
.instantiate_identity()
|
||||
.error_reported()
|
||||
{
|
||||
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
|
||||
} else {
|
||||
self.non_defining_use_in_defining_scope(item_def_id);
|
||||
}
|
||||
} else {
|
||||
self.found = Some(concrete_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -287,54 +242,42 @@ pub(super) fn find_opaque_ty_constraints_for_rpit<'tcx>(
|
|||
tcx: TyCtxt<'tcx>,
|
||||
def_id: LocalDefId,
|
||||
owner_def_id: LocalDefId,
|
||||
opaque_types_from: DefiningScopeKind,
|
||||
) -> Ty<'tcx> {
|
||||
let tables = tcx.typeck(owner_def_id);
|
||||
|
||||
// Check that all of the opaques we inferred during HIR are compatible.
|
||||
// FIXME: We explicitly don't check that the types inferred during HIR
|
||||
// typeck are compatible with the one that we infer during borrowck,
|
||||
// because that one actually sometimes has consts evaluated eagerly so
|
||||
// using strict type equality will fail.
|
||||
let mut hir_opaque_ty: Option<ty::OpaqueHiddenType<'tcx>> = None;
|
||||
if tables.tainted_by_errors.is_none() {
|
||||
for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
|
||||
if opaque_type_key.def_id != def_id {
|
||||
continue;
|
||||
}
|
||||
let concrete_type = tcx.erase_regions(
|
||||
hidden_type.remap_generic_params_to_declaration_params(opaque_type_key, tcx, true),
|
||||
);
|
||||
if let Some(prev) = &mut hir_opaque_ty {
|
||||
if concrete_type.ty != prev.ty {
|
||||
if let Ok(d) = prev.build_mismatch_error(&concrete_type, tcx) {
|
||||
d.emit();
|
||||
}
|
||||
}
|
||||
match opaque_types_from {
|
||||
DefiningScopeKind::HirTypeck => {
|
||||
let tables = tcx.typeck(owner_def_id);
|
||||
if let Some(guar) = tables.tainted_by_errors {
|
||||
Ty::new_error(tcx, guar)
|
||||
} else if let Some(hidden_ty) = tables.concrete_opaque_types.get(&def_id) {
|
||||
hidden_ty.ty
|
||||
} else {
|
||||
hir_opaque_ty = Some(concrete_type);
|
||||
// FIXME(-Znext-solver): This should not be necessary and we should
|
||||
// instead rely on inference variable fallback inside of typeck itself.
|
||||
|
||||
// We failed to resolve the opaque type or it
|
||||
// resolves to itself. We interpret this as the
|
||||
// no values of the hidden type ever being constructed,
|
||||
// so we can just make the hidden type be `!`.
|
||||
// For backwards compatibility reasons, we fall back to
|
||||
// `()` until we the diverging default is changed.
|
||||
Ty::new_diverging_default(tcx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mir_opaque_ty = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied();
|
||||
if let Some(mir_opaque_ty) = mir_opaque_ty {
|
||||
mir_opaque_ty.ty
|
||||
} else if let Some(guar) = tables.tainted_by_errors {
|
||||
// Some error in the owner fn prevented us from populating
|
||||
// the `concrete_opaque_types` table.
|
||||
Ty::new_error(tcx, guar)
|
||||
} else {
|
||||
// Fall back to the RPIT we inferred during HIR typeck
|
||||
if let Some(hir_opaque_ty) = hir_opaque_ty {
|
||||
hir_opaque_ty.ty
|
||||
} else {
|
||||
// We failed to resolve the opaque type or it
|
||||
// resolves to itself. We interpret this as the
|
||||
// no values of the hidden type ever being constructed,
|
||||
// so we can just make the hidden type be `!`.
|
||||
// For backwards compatibility reasons, we fall back to
|
||||
// `()` until we the diverging default is changed.
|
||||
Ty::new_diverging_default(tcx)
|
||||
DefiningScopeKind::MirBorrowck => {
|
||||
let borrowck_result = tcx.mir_borrowck(owner_def_id);
|
||||
if let Some(guar) = borrowck_result.tainted_by_errors {
|
||||
Ty::new_error(tcx, guar)
|
||||
} else if let Some(hidden_ty) = borrowck_result.concrete_opaque_types.get(&def_id) {
|
||||
hidden_ty.ty
|
||||
} else {
|
||||
let hir_ty = tcx.type_of_opaque_hir_typeck(def_id).instantiate_identity();
|
||||
if let Err(guar) = hir_ty.error_reported() {
|
||||
Ty::new_error(tcx, guar)
|
||||
} else {
|
||||
hir_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue