Auto merge of #106004 - fee1-dead-contrib:const-closures, r=oli-obk
Const closures cc https://github.com/rust-lang/rust/issues/106003
This commit is contained in:
commit
279f1c9d8c
@ -1307,6 +1307,7 @@ pub fn is_approximately_pattern(&self) -> bool {
|
||||
pub struct Closure {
|
||||
pub binder: ClosureBinder,
|
||||
pub capture_clause: CaptureBy,
|
||||
pub constness: Const,
|
||||
pub asyncness: Async,
|
||||
pub movability: Movability,
|
||||
pub fn_decl: P<FnDecl>,
|
||||
|
@ -1362,6 +1362,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
|
||||
ExprKind::Closure(box Closure {
|
||||
binder,
|
||||
capture_clause: _,
|
||||
constness,
|
||||
asyncness,
|
||||
movability: _,
|
||||
fn_decl,
|
||||
@ -1370,6 +1371,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
|
||||
fn_arg_span: _,
|
||||
}) => {
|
||||
vis.visit_closure_binder(binder);
|
||||
visit_constness(constness, vis);
|
||||
vis.visit_asyncness(asyncness);
|
||||
vis.visit_fn_decl(fn_decl);
|
||||
vis.visit_expr(body);
|
||||
|
@ -836,6 +836,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
|
||||
binder,
|
||||
capture_clause: _,
|
||||
asyncness: _,
|
||||
constness: _,
|
||||
movability: _,
|
||||
fn_decl,
|
||||
body,
|
||||
|
@ -209,6 +209,7 @@ pub(super) fn lower_expr_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
|
||||
ExprKind::Closure(box Closure {
|
||||
binder,
|
||||
capture_clause,
|
||||
constness,
|
||||
asyncness,
|
||||
movability,
|
||||
fn_decl,
|
||||
@ -233,6 +234,7 @@ pub(super) fn lower_expr_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
|
||||
binder,
|
||||
*capture_clause,
|
||||
e.id,
|
||||
*constness,
|
||||
*movability,
|
||||
fn_decl,
|
||||
body,
|
||||
@ -651,6 +653,7 @@ pub(super) fn make_async_expr(
|
||||
fn_decl_span: self.lower_span(span),
|
||||
fn_arg_span: None,
|
||||
movability: Some(hir::Movability::Static),
|
||||
constness: hir::Constness::NotConst,
|
||||
});
|
||||
|
||||
hir::ExprKind::Closure(c)
|
||||
@ -890,6 +893,7 @@ fn lower_expr_closure(
|
||||
binder: &ClosureBinder,
|
||||
capture_clause: CaptureBy,
|
||||
closure_id: NodeId,
|
||||
constness: Const,
|
||||
movability: Movability,
|
||||
decl: &FnDecl,
|
||||
body: &Expr,
|
||||
@ -927,6 +931,7 @@ fn lower_expr_closure(
|
||||
fn_decl_span: self.lower_span(fn_decl_span),
|
||||
fn_arg_span: Some(self.lower_span(fn_arg_span)),
|
||||
movability: generator_option,
|
||||
constness: self.lower_constness(constness),
|
||||
});
|
||||
|
||||
hir::ExprKind::Closure(c)
|
||||
@ -1041,6 +1046,7 @@ fn lower_expr_async_closure(
|
||||
fn_decl_span: self.lower_span(fn_decl_span),
|
||||
fn_arg_span: Some(self.lower_span(fn_arg_span)),
|
||||
movability: None,
|
||||
constness: hir::Constness::NotConst,
|
||||
});
|
||||
hir::ExprKind::Closure(c)
|
||||
}
|
||||
|
@ -1239,7 +1239,7 @@ fn lower_asyncness(&mut self, a: Async) -> hir::IsAsync {
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_constness(&mut self, c: Const) -> hir::Constness {
|
||||
pub(super) fn lower_constness(&mut self, c: Const) -> hir::Constness {
|
||||
match c {
|
||||
Const::Yes(_) => hir::Constness::Const,
|
||||
Const::No => hir::Constness::NotConst,
|
||||
|
@ -385,6 +385,14 @@ fn visit_expr(&mut self, e: &'a ast::Expr) {
|
||||
ast::ExprKind::TryBlock(_) => {
|
||||
gate_feature_post!(&self, try_blocks, e.span, "`try` expression is experimental");
|
||||
}
|
||||
ast::ExprKind::Closure(box ast::Closure { constness: ast::Const::Yes(_), .. }) => {
|
||||
gate_feature_post!(
|
||||
&self,
|
||||
const_closures,
|
||||
e.span,
|
||||
"const closures are experimental"
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
visit::walk_expr(self, e)
|
||||
|
@ -399,6 +399,7 @@ pub(super) fn print_expr_outer_attr_style(&mut self, expr: &ast::Expr, is_inline
|
||||
ast::ExprKind::Closure(box ast::Closure {
|
||||
binder,
|
||||
capture_clause,
|
||||
constness,
|
||||
asyncness,
|
||||
movability,
|
||||
fn_decl,
|
||||
@ -407,6 +408,7 @@ pub(super) fn print_expr_outer_attr_style(&mut self, expr: &ast::Expr, is_inline
|
||||
fn_arg_span: _,
|
||||
}) => {
|
||||
self.print_closure_binder(binder);
|
||||
self.print_constness(*constness);
|
||||
self.print_movability(*movability);
|
||||
self.print_asyncness(*asyncness);
|
||||
self.print_capture_clause(*capture_clause);
|
||||
|
@ -41,6 +41,7 @@ fn constness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::Constness {
|
||||
};
|
||||
if is_const { hir::Constness::Const } else { hir::Constness::NotConst }
|
||||
}
|
||||
hir::Node::Expr(e) if let hir::ExprKind::Closure(c) = e.kind => c.constness,
|
||||
_ => {
|
||||
if let Some(fn_kind) = node.fn_kind() {
|
||||
if fn_kind.constness() == hir::Constness::Const {
|
||||
|
@ -20,6 +20,7 @@
|
||||
#![feature(trusted_step)]
|
||||
#![feature(try_blocks)]
|
||||
#![feature(yeet_expr)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(is_some_and)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
|
@ -242,7 +242,7 @@ pub fn check_body(&mut self) {
|
||||
// impl trait is gone in MIR, so check the return type of a const fn by its signature
|
||||
// instead of the type of the return place.
|
||||
self.span = body.local_decls[RETURN_PLACE].source_info.span;
|
||||
let return_ty = tcx.fn_sig(def_id).output();
|
||||
let return_ty = self.ccx.fn_sig().output();
|
||||
self.check_local_or_return_ty(return_ty.skip_binder(), RETURN_PLACE);
|
||||
}
|
||||
|
||||
@ -730,6 +730,7 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
substs,
|
||||
span: *fn_span,
|
||||
from_hir_call: *from_hir_call,
|
||||
feature: Some(sym::const_trait_impl),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -782,6 +783,20 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
);
|
||||
return;
|
||||
}
|
||||
Ok(Some(ImplSource::Closure(data))) => {
|
||||
if !tcx.is_const_fn_raw(data.closure_def_id) {
|
||||
self.check_op(ops::FnCallNonConst {
|
||||
caller,
|
||||
callee,
|
||||
substs,
|
||||
span: *fn_span,
|
||||
from_hir_call: *from_hir_call,
|
||||
feature: None,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(Some(ImplSource::UserDefined(data))) => {
|
||||
let callee_name = tcx.item_name(callee);
|
||||
if let Some(&did) = tcx
|
||||
@ -802,6 +817,7 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
substs,
|
||||
span: *fn_span,
|
||||
from_hir_call: *from_hir_call,
|
||||
feature: None,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -844,6 +860,7 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
substs,
|
||||
span: *fn_span,
|
||||
from_hir_call: *from_hir_call,
|
||||
feature: None,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -903,6 +920,7 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
substs,
|
||||
span: *fn_span,
|
||||
from_hir_call: *from_hir_call,
|
||||
feature: None,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_middle::ty::{self, PolyFnSig, TyCtxt};
|
||||
use rustc_span::Symbol;
|
||||
|
||||
pub use self::qualifs::Qualif;
|
||||
@ -64,6 +64,17 @@ pub fn is_const_stable_const_fn(&self) -> bool {
|
||||
fn is_async(&self) -> bool {
|
||||
self.tcx.asyncness(self.def_id()).is_async()
|
||||
}
|
||||
|
||||
pub fn fn_sig(&self) -> PolyFnSig<'tcx> {
|
||||
let did = self.def_id().to_def_id();
|
||||
if self.tcx.is_closure(did) {
|
||||
let ty = self.tcx.type_of(did);
|
||||
let ty::Closure(_, substs) = ty.kind() else { bug!("type_of closure not ty::Closure") };
|
||||
substs.as_closure().sig()
|
||||
} else {
|
||||
self.tcx.fn_sig(did)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rustc_allow_const_fn_unstable(
|
||||
|
@ -111,6 +111,7 @@ pub struct FnCallNonConst<'tcx> {
|
||||
pub substs: SubstsRef<'tcx>,
|
||||
pub span: Span,
|
||||
pub from_hir_call: bool,
|
||||
pub feature: Option<Symbol>,
|
||||
}
|
||||
|
||||
impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
||||
@ -119,7 +120,7 @@ fn build_error(
|
||||
ccx: &ConstCx<'_, 'tcx>,
|
||||
_: Span,
|
||||
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
|
||||
let FnCallNonConst { caller, callee, substs, span, from_hir_call } = *self;
|
||||
let FnCallNonConst { caller, callee, substs, span, from_hir_call, feature } = *self;
|
||||
let ConstCx { tcx, param_env, .. } = *ccx;
|
||||
|
||||
let diag_trait = |err, self_ty: Ty<'_>, trait_id| {
|
||||
@ -318,6 +319,13 @@ macro_rules! error {
|
||||
ccx.const_kind(),
|
||||
));
|
||||
|
||||
if let Some(feature) = feature && ccx.tcx.sess.is_nightly_build() {
|
||||
err.help(&format!(
|
||||
"add `#![feature({})]` to the crate attributes to enable",
|
||||
feature,
|
||||
));
|
||||
}
|
||||
|
||||
if let ConstContext::Static(_) = ccx.const_kind() {
|
||||
err.note("consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell");
|
||||
}
|
||||
|
@ -533,6 +533,7 @@ pub fn lambda(&self, span: Span, ids: Vec<Ident>, body: P<ast::Expr>) -> P<ast::
|
||||
ast::ExprKind::Closure(Box::new(ast::Closure {
|
||||
binder: ast::ClosureBinder::NotPresent,
|
||||
capture_clause: ast::CaptureBy::Ref,
|
||||
constness: ast::Const::No,
|
||||
asyncness: ast::Async::No,
|
||||
movability: ast::Movability::Movable,
|
||||
fn_decl,
|
||||
|
@ -339,7 +339,9 @@ pub fn set(&self, features: &mut Features, span: Span) {
|
||||
(active, collapse_debuginfo, "1.65.0", Some(100758), None),
|
||||
/// Allows `async {}` expressions in const contexts.
|
||||
(active, const_async_blocks, "1.53.0", Some(85368), None),
|
||||
// Allows limiting the evaluation steps of const expressions
|
||||
/// Allows `const || {}` closures in const contexts.
|
||||
(incomplete, const_closures, "CURRENT_RUSTC_VERSION", Some(106003), None),
|
||||
/// Allows limiting the evaluation steps of const expressions
|
||||
(active, const_eval_limit, "1.43.0", Some(67217), None),
|
||||
/// Allows the definition of `const extern fn` and `const unsafe extern fn`.
|
||||
(active, const_extern_fn, "1.40.0", Some(64926), None),
|
||||
|
@ -938,6 +938,7 @@ pub struct Crate<'hir> {
|
||||
pub struct Closure<'hir> {
|
||||
pub def_id: LocalDefId,
|
||||
pub binder: ClosureBinder,
|
||||
pub constness: Constness,
|
||||
pub capture_clause: CaptureBy,
|
||||
pub bound_generic_params: &'hir [GenericParam<'hir>],
|
||||
pub fn_decl: &'hir FnDecl<'hir>,
|
||||
|
@ -742,6 +742,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
|
||||
fn_decl_span: _,
|
||||
fn_arg_span: _,
|
||||
movability: _,
|
||||
constness: _,
|
||||
}) => {
|
||||
walk_list!(visitor, visit_generic_param, bound_generic_params);
|
||||
visitor.visit_fn(FnKind::Closure, fn_decl, body, expression.span, expression.hir_id)
|
||||
|
@ -1464,6 +1464,7 @@ pub fn print_expr(&mut self, expr: &hir::Expr<'_>) {
|
||||
}
|
||||
hir::ExprKind::Closure(&hir::Closure {
|
||||
binder,
|
||||
constness,
|
||||
capture_clause,
|
||||
bound_generic_params,
|
||||
fn_decl,
|
||||
@ -1474,6 +1475,7 @@ pub fn print_expr(&mut self, expr: &hir::Expr<'_>) {
|
||||
def_id: _,
|
||||
}) => {
|
||||
self.print_closure_binder(binder, bound_generic_params);
|
||||
self.print_constness(constness);
|
||||
self.print_capture_clause(capture_clause);
|
||||
|
||||
self.print_closure_params(fn_decl, body);
|
||||
@ -2272,10 +2274,7 @@ pub fn print_ty_fn(
|
||||
}
|
||||
|
||||
pub fn print_fn_header_info(&mut self, header: hir::FnHeader) {
|
||||
match header.constness {
|
||||
hir::Constness::NotConst => {}
|
||||
hir::Constness::Const => self.word_nbsp("const"),
|
||||
}
|
||||
self.print_constness(header.constness);
|
||||
|
||||
match header.asyncness {
|
||||
hir::IsAsync::NotAsync => {}
|
||||
@ -2292,6 +2291,13 @@ pub fn print_fn_header_info(&mut self, header: hir::FnHeader) {
|
||||
self.word("fn")
|
||||
}
|
||||
|
||||
pub fn print_constness(&mut self, s: hir::Constness) {
|
||||
match s {
|
||||
hir::Constness::NotConst => {}
|
||||
hir::Constness::Const => self.word_nbsp("const"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_unsafety(&mut self, s: hir::Unsafety) {
|
||||
match s {
|
||||
hir::Unsafety::Normal => {}
|
||||
|
@ -1686,6 +1686,8 @@ fn encode_info_for_closure(&mut self, def_id: LocalDefId) {
|
||||
}
|
||||
|
||||
ty::Closure(_, substs) => {
|
||||
let constness = self.tcx.constness(def_id.to_def_id());
|
||||
self.tables.constness.set(def_id.to_def_id().index, constness);
|
||||
record!(self.tables.fn_sig[def_id.to_def_id()] <- substs.as_closure().sig());
|
||||
}
|
||||
|
||||
|
@ -485,7 +485,9 @@ pub fn body_const_context(self, def_id: LocalDefId) -> Option<ConstContext> {
|
||||
BodyOwnerKind::Static(mt) => ConstContext::Static(mt),
|
||||
|
||||
BodyOwnerKind::Fn if self.tcx.is_constructor(def_id.to_def_id()) => return None,
|
||||
BodyOwnerKind::Fn if self.tcx.is_const_fn_raw(def_id.to_def_id()) => {
|
||||
BodyOwnerKind::Fn | BodyOwnerKind::Closure
|
||||
if self.tcx.is_const_fn_raw(def_id.to_def_id()) =>
|
||||
{
|
||||
ConstContext::ConstFn
|
||||
}
|
||||
BodyOwnerKind::Fn if self.tcx.is_const_default_method(def_id.to_def_id()) => {
|
||||
|
@ -131,7 +131,9 @@ pub enum SelectionCandidate<'tcx> {
|
||||
|
||||
/// Implementation of a `Fn`-family trait by one of the anonymous types
|
||||
/// generated for an `||` expression.
|
||||
ClosureCandidate,
|
||||
ClosureCandidate {
|
||||
is_const: bool,
|
||||
},
|
||||
|
||||
/// Implementation of a `Generator` trait by one of the anonymous types
|
||||
/// generated for a generator.
|
||||
|
@ -2465,8 +2465,10 @@ pub fn is_object_safe(self, key: DefId) -> bool {
|
||||
|
||||
#[inline]
|
||||
pub fn is_const_fn_raw(self, def_id: DefId) -> bool {
|
||||
matches!(self.def_kind(def_id), DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..))
|
||||
&& self.constness(def_id) == hir::Constness::Const
|
||||
matches!(
|
||||
self.def_kind(def_id),
|
||||
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) | DefKind::Closure
|
||||
) && self.constness(def_id) == hir::Constness::Const
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1325,7 +1325,10 @@ fn parse_bottom_expr(&mut self) -> PResult<'a, P<Expr>> {
|
||||
self.parse_array_or_repeat_expr(Delimiter::Bracket)
|
||||
} else if self.check_path() {
|
||||
self.parse_path_start_expr()
|
||||
} else if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) {
|
||||
} else if self.check_keyword(kw::Move)
|
||||
|| self.check_keyword(kw::Static)
|
||||
|| self.check_const_closure()
|
||||
{
|
||||
self.parse_closure_expr()
|
||||
} else if self.eat_keyword(kw::If) {
|
||||
self.parse_if_expr()
|
||||
@ -2065,6 +2068,8 @@ fn parse_closure_expr(&mut self) -> PResult<'a, P<Expr>> {
|
||||
ClosureBinder::NotPresent
|
||||
};
|
||||
|
||||
let constness = self.parse_constness(Case::Sensitive);
|
||||
|
||||
let movability =
|
||||
if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
|
||||
|
||||
@ -2111,6 +2116,7 @@ fn parse_closure_expr(&mut self) -> PResult<'a, P<Expr>> {
|
||||
ExprKind::Closure(Box::new(ast::Closure {
|
||||
binder,
|
||||
capture_clause,
|
||||
constness,
|
||||
asyncness,
|
||||
movability,
|
||||
fn_decl,
|
||||
|
@ -736,6 +736,16 @@ fn check_const_arg(&mut self) -> bool {
|
||||
self.check_or_expected(self.token.can_begin_const_arg(), TokenType::Const)
|
||||
}
|
||||
|
||||
fn check_const_closure(&self) -> bool {
|
||||
self.is_keyword_ahead(0, &[kw::Const])
|
||||
&& self.look_ahead(1, |t| match &t.kind {
|
||||
token::Ident(kw::Move | kw::Static | kw::Async, _)
|
||||
| token::OrOr
|
||||
| token::BinOp(token::Or) => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_inline_const(&self, dist: usize) -> bool {
|
||||
self.is_keyword_ahead(dist, &[kw::Const])
|
||||
&& self.look_ahead(dist + 1, |t| match &t.kind {
|
||||
|
@ -498,6 +498,7 @@
|
||||
console,
|
||||
const_allocate,
|
||||
const_async_blocks,
|
||||
const_closures,
|
||||
const_compare_raw_pointers,
|
||||
const_constructor,
|
||||
const_deallocate,
|
||||
|
@ -255,18 +255,19 @@ fn assemble_closure_candidates(
|
||||
// touch bound regions, they just capture the in-scope
|
||||
// type/region parameters
|
||||
match *obligation.self_ty().skip_binder().kind() {
|
||||
ty::Closure(_, closure_substs) => {
|
||||
ty::Closure(def_id, closure_substs) => {
|
||||
let is_const = self.tcx().is_const_fn_raw(def_id);
|
||||
debug!(?kind, ?obligation, "assemble_unboxed_candidates");
|
||||
match self.infcx.closure_kind(closure_substs) {
|
||||
Some(closure_kind) => {
|
||||
debug!(?closure_kind, "assemble_unboxed_candidates");
|
||||
if closure_kind.extends(kind) {
|
||||
candidates.vec.push(ClosureCandidate);
|
||||
candidates.vec.push(ClosureCandidate { is_const });
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("assemble_unboxed_candidates: closure_kind not yet known");
|
||||
candidates.vec.push(ClosureCandidate);
|
||||
candidates.vec.push(ClosureCandidate { is_const });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ pub(super) fn confirm_candidate(
|
||||
ImplSource::Object(data)
|
||||
}
|
||||
|
||||
ClosureCandidate => {
|
||||
ClosureCandidate { .. } => {
|
||||
let vtable_closure = self.confirm_closure_candidate(obligation)?;
|
||||
ImplSource::Closure(vtable_closure)
|
||||
}
|
||||
|
@ -1365,15 +1365,18 @@ fn filter_impls(
|
||||
// const param
|
||||
ParamCandidate(trait_pred) if trait_pred.is_const_if_const() => {}
|
||||
// const projection
|
||||
ProjectionCandidate(_, ty::BoundConstness::ConstIfConst) => {}
|
||||
ProjectionCandidate(_, ty::BoundConstness::ConstIfConst)
|
||||
// auto trait impl
|
||||
AutoImplCandidate => {}
|
||||
| AutoImplCandidate
|
||||
// generator / future, this will raise error in other places
|
||||
// or ignore error with const_async_blocks feature
|
||||
GeneratorCandidate => {}
|
||||
FutureCandidate => {}
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
// FnDef where the function is const
|
||||
FnPointerCandidate { is_const: true } => {}
|
||||
| FnPointerCandidate { is_const: true }
|
||||
| ConstDestructCandidate(_)
|
||||
| ClosureCandidate { is_const: true } => {}
|
||||
|
||||
FnPointerCandidate { is_const: false } => {
|
||||
if let ty::FnDef(def_id, _) = obligation.self_ty().skip_binder().kind() && tcx.trait_of_item(*def_id).is_some() {
|
||||
// Trait methods are not seen as const unless the trait is implemented as const.
|
||||
@ -1382,7 +1385,7 @@ fn filter_impls(
|
||||
continue
|
||||
}
|
||||
}
|
||||
ConstDestructCandidate(_) => {}
|
||||
|
||||
_ => {
|
||||
// reject all other types of candidates
|
||||
continue;
|
||||
@ -1844,7 +1847,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
(
|
||||
ParamCandidate(ref cand),
|
||||
ImplCandidate(..)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
@ -1863,7 +1866,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
}
|
||||
(
|
||||
ImplCandidate(_)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
@ -1894,7 +1897,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
(
|
||||
ObjectCandidate(_) | ProjectionCandidate(..),
|
||||
ImplCandidate(..)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
@ -1907,7 +1910,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
|
||||
(
|
||||
ImplCandidate(..)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
@ -1989,7 +1992,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
// Everything else is ambiguous
|
||||
(
|
||||
ImplCandidate(_)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
@ -1999,7 +2002,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
| BuiltinCandidate { has_nested: true }
|
||||
| TraitAliasCandidate,
|
||||
ImplCandidate(_)
|
||||
| ClosureCandidate
|
||||
| ClosureCandidate { .. }
|
||||
| GeneratorCandidate
|
||||
| FutureCandidate
|
||||
| FnPointerCandidate { .. }
|
||||
|
@ -1234,17 +1234,23 @@ pub const fn min_by_key<T, F: ~const FnMut(&T) -> K, K: ~const Ord>(v1: T, v2: T
|
||||
F: ~const Destruct,
|
||||
K: ~const Destruct,
|
||||
{
|
||||
const fn imp<T, F: ~const FnMut(&T) -> K, K: ~const Ord>(
|
||||
f: &mut F,
|
||||
(v1, v2): (&T, &T),
|
||||
) -> Ordering
|
||||
where
|
||||
T: ~const Destruct,
|
||||
K: ~const Destruct,
|
||||
{
|
||||
f(v1).cmp(&f(v2))
|
||||
cfg_if! {
|
||||
if #[cfg(bootstrap)] {
|
||||
const fn imp<T, F: ~const FnMut(&T) -> K, K: ~const Ord>(
|
||||
f: &mut F,
|
||||
(v1, v2): (&T, &T),
|
||||
) -> Ordering
|
||||
where
|
||||
T: ~const Destruct,
|
||||
K: ~const Destruct,
|
||||
{
|
||||
f(v1).cmp(&f(v2))
|
||||
}
|
||||
min_by(v1, v2, ConstFnMutClosure::new(&mut f, imp))
|
||||
} else {
|
||||
min_by(v1, v2, const |v1, v2| f(v1).cmp(&f(v2)))
|
||||
}
|
||||
}
|
||||
min_by(v1, v2, ConstFnMutClosure::new(&mut f, imp))
|
||||
}
|
||||
|
||||
/// Compares and returns the maximum of two values.
|
||||
|
@ -191,6 +191,7 @@
|
||||
#![feature(cfg_sanitize)]
|
||||
#![feature(cfg_target_has_atomic)]
|
||||
#![feature(cfg_target_has_atomic_equal_alignment)]
|
||||
#![cfg_attr(not(bootstrap), feature(const_closures))]
|
||||
#![feature(const_fn_floating_point_arithmetic)]
|
||||
#![feature(const_mut_refs)]
|
||||
#![feature(const_precise_live_drops)]
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
pub(crate) fn rewrite_closure(
|
||||
binder: &ast::ClosureBinder,
|
||||
constness: ast::Const,
|
||||
capture: ast::CaptureBy,
|
||||
is_async: &ast::Async,
|
||||
movability: ast::Movability,
|
||||
@ -38,7 +39,7 @@ pub(crate) fn rewrite_closure(
|
||||
debug!("rewrite_closure {:?}", body);
|
||||
|
||||
let (prefix, extra_offset) = rewrite_closure_fn_decl(
|
||||
binder, capture, is_async, movability, fn_decl, body, span, context, shape,
|
||||
binder, constness, capture, is_async, movability, fn_decl, body, span, context, shape,
|
||||
)?;
|
||||
// 1 = space between `|...|` and body.
|
||||
let body_shape = shape.offset_left(extra_offset)?;
|
||||
@ -230,6 +231,7 @@ fn rewrite_closure_block(
|
||||
// Return type is (prefix, extra_offset)
|
||||
fn rewrite_closure_fn_decl(
|
||||
binder: &ast::ClosureBinder,
|
||||
constness: ast::Const,
|
||||
capture: ast::CaptureBy,
|
||||
asyncness: &ast::Async,
|
||||
movability: ast::Movability,
|
||||
@ -250,6 +252,12 @@ fn rewrite_closure_fn_decl(
|
||||
ast::ClosureBinder::NotPresent => "".to_owned(),
|
||||
};
|
||||
|
||||
let const_ = if matches!(constness, ast::Const::Yes(_)) {
|
||||
"const "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let immovable = if movability == ast::Movability::Static {
|
||||
"static "
|
||||
} else {
|
||||
@ -264,7 +272,7 @@ fn rewrite_closure_fn_decl(
|
||||
// 4 = "|| {".len(), which is overconservative when the closure consists of
|
||||
// a single expression.
|
||||
let nested_shape = shape
|
||||
.shrink_left(binder.len() + immovable.len() + is_async.len() + mover.len())?
|
||||
.shrink_left(binder.len() + const_.len() + immovable.len() + is_async.len() + mover.len())?
|
||||
.sub_width(4)?;
|
||||
|
||||
// 1 = |
|
||||
@ -302,7 +310,10 @@ fn rewrite_closure_fn_decl(
|
||||
.tactic(tactic)
|
||||
.preserve_newline(true);
|
||||
let list_str = write_list(&item_vec, &fmt)?;
|
||||
let mut prefix = format!("{}{}{}{}|{}|", binder, immovable, is_async, mover, list_str);
|
||||
let mut prefix = format!(
|
||||
"{}{}{}{}{}|{}|",
|
||||
binder, const_, immovable, is_async, mover, list_str
|
||||
);
|
||||
|
||||
if !ret_str.is_empty() {
|
||||
if prefix.contains('\n') {
|
||||
@ -329,6 +340,7 @@ pub(crate) fn rewrite_last_closure(
|
||||
if let ast::ExprKind::Closure(ref closure) = expr.kind {
|
||||
let ast::Closure {
|
||||
ref binder,
|
||||
constness,
|
||||
capture_clause,
|
||||
ref asyncness,
|
||||
movability,
|
||||
@ -349,6 +361,7 @@ pub(crate) fn rewrite_last_closure(
|
||||
};
|
||||
let (prefix, extra_offset) = rewrite_closure_fn_decl(
|
||||
binder,
|
||||
constness,
|
||||
capture_clause,
|
||||
asyncness,
|
||||
movability,
|
||||
|
@ -205,6 +205,7 @@ pub(crate) fn format_expr(
|
||||
}
|
||||
ast::ExprKind::Closure(ref cl) => closures::rewrite_closure(
|
||||
&cl.binder,
|
||||
cl.constness,
|
||||
cl.capture_clause,
|
||||
&cl.asyncness,
|
||||
cl.movability,
|
||||
|
@ -126,6 +126,7 @@ fn iter_exprs(depth: usize, f: &mut dyn FnMut(P<Expr>)) {
|
||||
g(ExprKind::Closure(Box::new(Closure {
|
||||
binder: ClosureBinder::NotPresent,
|
||||
capture_clause: CaptureBy::Value,
|
||||
constness: Const::No,
|
||||
asyncness: Async::No,
|
||||
movability: Movability::Movable,
|
||||
fn_decl: decl.clone(),
|
||||
|
@ -22,6 +22,7 @@ LL | field2: SafeEnum::Variant4("str".to_string())
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: calls in statics are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
= note: consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell
|
||||
|
||||
error[E0010]: allocations are not allowed in statics
|
||||
|
@ -6,6 +6,7 @@ LL | (||1usize)()
|
||||
|
|
||||
= note: closures need an RFC before allowed to be called in constants
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -22,6 +22,7 @@ LL | for i in 0..x {
|
||||
note: impl defined here, but it is not `const`
|
||||
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error[E0658]: mutable references are not allowed in constant functions
|
||||
--> $DIR/const-fn-error.rs:5:14
|
||||
@ -39,6 +40,7 @@ LL | for i in 0..x {
|
||||
| ^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
@ -7,6 +7,7 @@ LL | for _ in 0..5 {}
|
||||
note: impl defined here, but it is not `const`
|
||||
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error[E0015]: cannot call non-const fn `<std::ops::Range<i32> as Iterator>::next` in constants
|
||||
--> $DIR/const-for.rs:5:14
|
||||
@ -15,6 +16,7 @@ LL | for _ in 0..5 {}
|
||||
| ^^^^
|
||||
|
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
@ -6,6 +6,7 @@ LL | const { (|| {})() } => {}
|
||||
|
|
||||
= note: closures need an RFC before allowed to be called in constants
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -6,6 +6,7 @@ LL | || -> u8 { 5 }()
|
||||
|
|
||||
= note: closures need an RFC before allowed to be called in constants
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -6,6 +6,7 @@ LL | const fn foo() { (||{})() }
|
||||
|
|
||||
= note: closures need an RFC before allowed to be called in constant functions
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: function pointer calls are not allowed in constant functions
|
||||
--> $DIR/issue-56164.rs:5:5
|
||||
|
@ -6,6 +6,7 @@ LL | a: [(); (|| { 0 })()]
|
||||
|
|
||||
= note: closures need an RFC before allowed to be called in constants
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -8,12 +8,14 @@ const fn f(a: &u8, b: &u8) -> bool {
|
||||
*a == *b
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
}
|
||||
|
||||
const fn g(a: &&&&i64, b: &&&&i64) -> bool {
|
||||
****a == ****b
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
}
|
||||
|
||||
const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
|
||||
@ -21,6 +23,7 @@ const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
|
||||
if *l == *r {
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
a = at;
|
||||
b = bt;
|
||||
} else {
|
||||
|
@ -8,12 +8,14 @@ const fn f(a: &u8, b: &u8) -> bool {
|
||||
a == b
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
}
|
||||
|
||||
const fn g(a: &&&&i64, b: &&&&i64) -> bool {
|
||||
a == b
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
}
|
||||
|
||||
const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
|
||||
@ -21,6 +23,7 @@ const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
|
||||
if l == r {
|
||||
//~^ ERROR: cannot call non-const operator in constant functions [E0015]
|
||||
//~| HELP: consider dereferencing here
|
||||
//~| HELP: add `#![feature(const_trait_impl)]`
|
||||
a = at;
|
||||
b = bt;
|
||||
} else {
|
||||
|
@ -5,30 +5,33 @@ LL | a == b
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | *a == *b
|
||||
| + +
|
||||
|
||||
error[E0015]: cannot call non-const operator in constant functions
|
||||
--> $DIR/issue-90870.rs:14:5
|
||||
--> $DIR/issue-90870.rs:15:5
|
||||
|
|
||||
LL | a == b
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | ****a == ****b
|
||||
| ++++ ++++
|
||||
|
||||
error[E0015]: cannot call non-const operator in constant functions
|
||||
--> $DIR/issue-90870.rs:21:12
|
||||
--> $DIR/issue-90870.rs:23:12
|
||||
|
|
||||
LL | if l == r {
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | if *l == *r {
|
||||
|
@ -47,6 +47,7 @@ LL | [(); { for _ in 0usize.. {}; 0}];
|
||||
note: impl defined here, but it is not `const`
|
||||
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error[E0658]: mutable references are not allowed in constants
|
||||
--> $DIR/issue-52443.rs:9:21
|
||||
@ -64,6 +65,7 @@ LL | [(); { for _ in 0usize.. {}; 0}];
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to 6 previous errors; 1 warning emitted
|
||||
|
||||
|
@ -7,6 +7,6 @@ fn main() {
|
||||
enum Foo { Bar }
|
||||
fn foo(x: impl Iterator<Item = Foo>) {
|
||||
for <Foo>::Bar in x {}
|
||||
//~^ ERROR expected one of `move`, `static`, `|`
|
||||
//~^ ERROR expected one of `const`, `move`, `static`, `|`
|
||||
//~^^ ERROR `for<...>` binders for closures are experimental
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
error: expected one of `move`, `static`, `|`, or `||`, found `::`
|
||||
error: expected one of `const`, `move`, `static`, `|`, or `||`, found `::`
|
||||
--> $DIR/recover-quantified-closure.rs:9:14
|
||||
|
|
||||
LL | for <Foo>::Bar in x {}
|
||||
| ^^ expected one of `move`, `static`, `|`, or `||`
|
||||
| ^^ expected one of `const`, `move`, `static`, `|`, or `||`
|
||||
|
||||
error[E0658]: `for<...>` binders for closures are experimental
|
||||
--> $DIR/recover-quantified-closure.rs:2:5
|
||||
|
@ -5,6 +5,7 @@ LL | let array: [usize; Dim3::dim()]
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error[E0015]: cannot call non-const fn `<Dim3 as Dim>::dim` in constants
|
||||
--> $DIR/issue-39559-2.rs:16:15
|
||||
@ -13,6 +14,7 @@ LL | = [0; Dim3::dim()];
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
10
tests/ui/rfc-2632-const-trait-impl/call.rs
Normal file
10
tests/ui/rfc-2632-const-trait-impl/call.rs
Normal file
@ -0,0 +1,10 @@
|
||||
// check-pass
|
||||
|
||||
#![feature(const_closures, const_trait_impl)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
pub const _: () = {
|
||||
assert!((const || true)());
|
||||
};
|
||||
|
||||
fn main() {}
|
@ -7,6 +7,7 @@ LL | pub struct S(A);
|
||||
| ^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to previous error
|
||||
|
@ -5,6 +5,7 @@ LL | Const.func();
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
5
tests/ui/rfc-2632-const-trait-impl/gate.rs
Normal file
5
tests/ui/rfc-2632-const-trait-impl/gate.rs
Normal file
@ -0,0 +1,5 @@
|
||||
// gate-test-const_closures
|
||||
fn main() {
|
||||
(const || {})();
|
||||
//~^ ERROR: const closures are experimental
|
||||
}
|
12
tests/ui/rfc-2632-const-trait-impl/gate.stderr
Normal file
12
tests/ui/rfc-2632-const-trait-impl/gate.stderr
Normal file
@ -0,0 +1,12 @@
|
||||
error[E0658]: const closures are experimental
|
||||
--> $DIR/gate.rs:3:6
|
||||
|
|
||||
LL | (const || {})();
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #106003 <https://github.com/rust-lang/rust/issues/106003> for more information
|
||||
= help: add `#![feature(const_closures)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0658`.
|
@ -0,0 +1,15 @@
|
||||
#![feature(const_closures, const_trait_impl)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
trait Foo {
|
||||
fn foo(&self);
|
||||
}
|
||||
|
||||
impl Foo for () {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
(const || { (()).foo() })();
|
||||
//~^ ERROR: cannot call non-const fn
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
error[E0015]: cannot call non-const fn `<() as Foo>::foo` in constant functions
|
||||
--> $DIR/non-const-op-const-closure-non-const-outer.rs:13:22
|
||||
|
|
||||
LL | (const || { (()).foo() })();
|
||||
| ^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0015`.
|
@ -5,6 +5,7 @@ LL | Unstable::func();
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -5,6 +5,7 @@ LL | Default::default()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
|
||||
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user