Skip to content

Rollup of 9 pull requests #120903

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 29 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
87ea0d7
Suppress suggestions in derive macro
long-long-float Dec 12, 2023
4909258
Check in push_suggestion
long-long-float Jan 24, 2024
8cc5084
Update UI tests that generates duplicated result
long-long-float Jan 27, 2024
1a0200e
large_assignments: Add copy variant of Box, Rc, Arc check
Enselic Feb 8, 2024
4e7941c
Check with overlaps_or_adjacent
long-long-float Feb 8, 2024
42c4f10
Fix test results
long-long-float Feb 9, 2024
d96f0c3
simd intrinsics: add simd_shuffle_generic
RalfJung Dec 22, 2023
5219af6
add more missing simd intrinsics
RalfJung Dec 28, 2023
aa64c73
simd_scatter: mention left-to-right order
RalfJung Dec 28, 2023
3bc490d
various docs tweaks
RalfJung Feb 10, 2024
0815067
Take empty `where` into account when suggesting predicates
gurry Feb 10, 2024
18ed966
interpret/write_discriminant: when encoding niched variant, ensure th…
RalfJung Feb 10, 2024
d56f3b6
interpret: rename ReadExternStatic → ExternStatic
RalfJung Feb 10, 2024
e2979a8
large_assignments: Allow moves into functions
Enselic Feb 8, 2024
a4fbd01
tests/ui/lint/large_assignments: only-x86_64 -> only-64bit
Enselic Feb 8, 2024
44616e1
Add test for the issue
long-long-float Feb 10, 2024
1e59e66
Fix to use for loop
long-long-float Feb 10, 2024
fd470e5
Adapt `llvm-has-rust-patches` validation to take `llvm-config` into a…
TimNN Feb 10, 2024
e330fe9
don't skip coercions for types with errors
Feb 10, 2024
86ddb53
Print kind of coroutine closure
compiler-errors Feb 10, 2024
5f9457c
Rollup merge of #119213 - RalfJung:simd_shuffle, r=workingjubilee
matthiaskrgr Feb 11, 2024
e525bc9
Rollup merge of #120272 - long-long-float:suppress-suggestions-in-der…
matthiaskrgr Feb 11, 2024
fd287d2
Rollup merge of #120773 - Enselic:copy-vs-move, r=oli-obk
matthiaskrgr Feb 11, 2024
0171057
Rollup merge of #120874 - gurry:120838-extra-where-in-suggestion, r=f…
matthiaskrgr Feb 11, 2024
e82e087
Rollup merge of #120882 - RalfJung:set-discriminant, r=compiler-errors
matthiaskrgr Feb 11, 2024
09bbcd6
Rollup merge of #120883 - RalfJung:extern-static-err, r=oli-obk
matthiaskrgr Feb 11, 2024
955cbdf
Rollup merge of #120890 - TimNN:relax-patches-check, r=onur-ozkan
matthiaskrgr Feb 11, 2024
302301b
Rollup merge of #120895 - lukas-code:error-coercions-ice, r=compiler-…
matthiaskrgr Feb 11, 2024
870435b
Rollup merge of #120896 - compiler-errors:coro-closure-kind, r=oli-obk
matthiaskrgr Feb 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ const_eval_error = {$error_kind ->
const_eval_exact_div_has_remainder =
exact_div: {$a} cannot be divided by {$b} without remainder

const_eval_extern_static =
cannot access extern static ({$did})
const_eval_fn_ptr_call =
function pointers need an RFC before allowed to be called in {const_eval_const_context}s
const_eval_for_loop_into_iter_non_const =
Expand Down Expand Up @@ -172,6 +174,10 @@ const_eval_invalid_meta =
invalid metadata in wide pointer: total size is bigger than largest supported object
const_eval_invalid_meta_slice =
invalid metadata in wide pointer: slice is bigger than largest supported object

const_eval_invalid_niched_enum_variant_written =
trying to set discriminant of a {$ty} to the niched variant, but the value does not match

const_eval_invalid_str =
this string is not valid UTF-8: {$err}
const_eval_invalid_tag =
Expand Down Expand Up @@ -298,8 +304,6 @@ const_eval_raw_ptr_to_int =
.note = at compile-time, pointers do not have an integer value
.note2 = avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior

const_eval_read_extern_static =
cannot read from extern static ({$did})
const_eval_read_pointer_as_int =
unable to turn pointer into integer
const_eval_realloc_or_alloc_with_offset =
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written,
UninhabitedEnumVariantRead(_) => const_eval_uninhabited_enum_variant_read,
InvalidNichedEnumVariantWritten { .. } => {
const_eval_invalid_niched_enum_variant_written
}
AbiMismatchArgument { .. } => const_eval_incompatible_types,
AbiMismatchReturn { .. } => const_eval_incompatible_return_types,
}
Expand Down Expand Up @@ -585,6 +588,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
builder.arg("target_size", info.target_size);
builder.arg("data_size", info.data_size);
}
InvalidNichedEnumVariantWritten { enum_ty } => {
builder.arg("ty", enum_ty.to_string());
}
AbiMismatchArgument { caller_ty, callee_ty }
| AbiMismatchReturn { caller_ty, callee_ty } => {
builder.arg("caller_ty", caller_ty.to_string());
Expand Down Expand Up @@ -793,7 +799,7 @@ impl ReportErrorExt for UnsupportedOpInfo {
UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_copy,
UnsupportedOpInfo::ReadPointerAsInt(_) => const_eval_read_pointer_as_int,
UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static,
UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static,
UnsupportedOpInfo::ExternStatic(_) => const_eval_extern_static,
}
}
fn add_args<G: EmissionGuarantee>(self, _: &DiagCtxt, builder: &mut DiagnosticBuilder<'_, G>) {
Expand All @@ -812,7 +818,7 @@ impl ReportErrorExt for UnsupportedOpInfo {
OverwritePartialPointer(ptr) | ReadPartialPointer(ptr) => {
builder.arg("ptr", ptr);
}
ThreadLocalStatic(did) | ReadExternStatic(did) => {
ThreadLocalStatic(did) | ExternStatic(did) => {
builder.arg("did", format!("{did:?}"));
}
}
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_const_eval/src/interpret/discriminant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Write result.
let niche_dest = self.project_field(dest, tag_field)?;
self.write_immediate(*tag_val, &niche_dest)?;
} else {
// The untagged variant is implicitly encoded simply by having a value that is
// outside the niche variants. But what if the data stored here does not
// actually encode this variant? That would be bad! So let's double-check...
let actual_variant = self.read_discriminant(&dest.to_op(self)?)?;
if actual_variant != variant_index {
throw_ub!(InvalidNichedEnumVariantWritten { enum_ty: dest.layout().ty });
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if self.tcx.is_foreign_item(def_id) {
// This is unreachable in Miri, but can happen in CTFE where we actually *do* support
// referencing arbitrary (declared) extern statics.
throw_unsup!(ReadExternStatic(def_id));
throw_unsup!(ExternStatic(def_id));
}

// We don't give a span -- statics don't need that, they cannot be generic or associated.
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_errors/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,17 @@ impl Diagnostic {

/// Helper for pushing to `self.suggestions`, if available (not disable).
fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
for subst in &suggestion.substitutions {
for part in &subst.parts {
let span = part.span;
let call_site = span.ctxt().outer_expn_data().call_site;
if span.in_derive_expansion() && span.overlaps_or_adjacent(call_site) {
// Ignore if spans is from derive macro.
return;
}
}
}

if let Ok(suggestions) = &mut self.suggestions {
suggestions.push(suggestion);
}
Expand Down
11 changes: 0 additions & 11 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
let b = self.shallow_resolve(b);
debug!("Coerce.tys({:?} => {:?})", a, b);

// Just ignore error types.
if let Err(guar) = (a, b).error_reported() {
// Best-effort try to unify these types -- we're already on the error path,
// so this will have the side-effect of making sure we have no ambiguities
// due to `[type error]` and `_` not coercing together.
let _ = self.commit_if_ok(|_| {
self.at(&self.cause, self.param_env).eq(DefineOpaqueTypes::Yes, a, b)
});
return success(vec![], Ty::new_error(self.fcx.tcx, guar), vec![]);
}

// Coercing from `!` to any type is allowed:
if a.is_never() {
return success(simple(Adjust::NeverToAny)(b), b, vec![]);
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ pub enum UndefinedBehaviorInfo<'tcx> {
UninhabitedEnumVariantWritten(VariantIdx),
/// An uninhabited enum variant is projected.
UninhabitedEnumVariantRead(VariantIdx),
/// Trying to set discriminant to the niched variant, but the value does not match.
InvalidNichedEnumVariantWritten { enum_ty: Ty<'tcx> },
/// ABI-incompatible argument types.
AbiMismatchArgument { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> },
/// ABI-incompatible return types.
Expand Down Expand Up @@ -468,7 +470,7 @@ pub enum UnsupportedOpInfo {
/// Accessing thread local statics
ThreadLocalStatic(DefId),
/// Accessing an unsupported extern static.
ReadExternStatic(DefId),
ExternStatic(DefId),
}

/// Error information for when the program exhausted the resources granted to it
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_middle/src/ty/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,17 @@ pub fn suggest_constraining_type_params<'a>(
// trait Foo<T=()> {... }
// - insert: `where T: Zar`
if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
// If we are here and the where clause span is of non-zero length
// it means we're dealing with an empty where clause like this:
// fn foo<X>(x: X) where { ... }
// In that case we don't want to add another "where" (Fixes #120838)
let where_prefix = if generics.where_clause_span.is_empty() { " where" } else { "" };

// Suggest a bound, but there is no existing `where` clause *and* the type param has a
// default (`<T=Foo>`), so we suggest adding `where T: Bar`.
suggestions.push((
generics.tail_span_for_predicate_suggestion(),
format!(" where {param_name}: {constraint}"),
format!("{where_prefix} {param_name}: {constraint}"),
SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
));
continue;
Expand Down
19 changes: 18 additions & 1 deletion compiler/rustc_middle/src/ty/print/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,24 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
ty::CoroutineClosure(did, args) => {
p!(write("{{"));
if !self.should_print_verbose() {
p!(write("coroutine-closure"));
match self.tcx().coroutine_kind(self.tcx().coroutine_for_closure(did)).unwrap()
{
hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async,
hir::CoroutineSource::Closure,
) => p!("async closure"),
hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::AsyncGen,
hir::CoroutineSource::Closure,
) => p!("async gen closure"),
hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Gen,
hir::CoroutineSource::Closure,
) => p!("gen closure"),
_ => unreachable!(
"coroutine from coroutine-closure should have CoroutineSource::Closure"
),
}
// FIXME(eddyb) should use `def_span`.
if let Some(did) = did.as_local() {
if self.tcx().sess.opts.unstable_opts.span_free_formats {
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_monomorphize/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,15 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
debug!(?def_id, ?fn_span);

for arg in args {
if let Some(too_large_size) = self.operand_size_if_too_large(limit, &arg.node) {
// Moving args into functions is typically implemented with pointer
// passing at the llvm-ir level and not by memcpy's. So always allow
// moving args into functions.
let operand: &mir::Operand<'tcx> = &arg.node;
if let mir::Operand::Move(_) = operand {
continue;
}

if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
self.lint_large_assignment(limit.0, too_large_size, location, arg.span);
};
}
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_span/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,13 @@ impl Span {
span.lo < other.hi && other.lo < span.hi
}

/// Returns `true` if `self` touches or adjoins `other`.
pub fn overlaps_or_adjacent(self, other: Span) -> bool {
let span = self.data();
let other = other.data();
span.lo <= other.hi && other.lo <= span.hi
}

/// Returns `true` if the spans are equal with regards to the source text.
///
/// Use this instead of `==` when either span could be generated code,
Expand Down
54 changes: 51 additions & 3 deletions library/core/src/intrinsics/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,27 @@ extern "platform-intrinsic" {
///
/// `T` must be a vector.
///
/// `U` must be a const array of `i32`s.
/// `U` must be a **const** array of `i32`s. This means it must either refer to a named
/// const or be given as an inline const expression (`const { ... }`).
///
/// `V` must be a vector with the same element type as `T` and the same length as `U`.
///
/// Concatenates `x` and `y`, then returns a new vector such that each element is selected from
/// the concatenation by the matching index in `idx`.
/// Returns a new vector such that element `i` is selected from `xy[idx[i]]`, where `xy`
/// is the concatenation of `x` and `y`. It is a compile-time error if `idx[i]` is out-of-bounds
/// of `xy`.
pub fn simd_shuffle<T, U, V>(x: T, y: T, idx: U) -> V;

/// Shuffle two vectors by const indices.
///
/// `T` must be a vector.
///
/// `U` must be a vector with the same element type as `T` and the same length as `IDX`.
///
/// Returns a new vector such that element `i` is selected from `xy[IDX[i]]`, where `xy`
/// is the concatenation of `x` and `y`. It is a compile-time error if `IDX[i]` is out-of-bounds
/// of `xy`.
pub fn simd_shuffle_generic<T, U, const IDX: &'static [u32]>(x: T, y: T) -> U;

/// Read a vector of pointers.
///
/// `T` must be a vector.
Expand Down Expand Up @@ -232,6 +245,9 @@ extern "platform-intrinsic" {
/// corresponding value in `val` to the pointer.
/// Otherwise if the corresponding value in `mask` is `0`, do nothing.
///
/// The stores happen in left-to-right order.
/// (This is relevant in case two of the stores overlap.)
///
/// # Safety
/// Unmasked values in `T` must be writeable as if by `<ptr>::write` (e.g. aligned to the element
/// type).
Expand Down Expand Up @@ -468,4 +484,36 @@ extern "platform-intrinsic" {
///
/// `T` must be a vector of integers.
pub fn simd_cttz<T>(x: T) -> T;

/// Round up each element to the next highest integer-valued float.
///
/// `T` must be a vector of floats.
pub fn simd_ceil<T>(x: T) -> T;

/// Round down each element to the next lowest integer-valued float.
///
/// `T` must be a vector of floats.
pub fn simd_floor<T>(x: T) -> T;

/// Round each element to the closest integer-valued float.
/// Ties are resolved by rounding away from 0.
///
/// `T` must be a vector of floats.
pub fn simd_round<T>(x: T) -> T;

/// Return the integer part of each element as an integer-valued float.
/// In other words, non-integer values are truncated towards zero.
///
/// `T` must be a vector of floats.
pub fn simd_trunc<T>(x: T) -> T;

/// Takes the square root of each element.
///
/// `T` must be a vector of floats.
pub fn simd_fsqrt<T>(x: T) -> T;

/// Computes `(x*y) + z` for each element, but without any intermediate rounding.
///
/// `T` must be a vector of floats.
pub fn simd_fma<T>(x: T, y: T, z: T) -> T;
}
7 changes: 3 additions & 4 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1810,10 +1810,9 @@ impl Config {
target.llvm_config = Some(config.src.join(s));
}
if let Some(patches) = cfg.llvm_has_rust_patches {
assert_eq!(
config.submodules,
Some(false),
"cannot set `llvm-has-rust-patches` for a managed submodule (set `build.submodules = false` if you want to apply patches)"
assert!(
config.submodules == Some(false) || cfg.llvm_config.is_some(),
"use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided"
);
target.llvm_has_rust_patches = Some(patches);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![feature(core_intrinsics)]
#![feature(custom_mir)]

use std::intrinsics::mir::*;
use std::num::NonZeroI32;

// We define our own option type so that we can control the varian indices.
#[allow(unused)]
enum Option<T> {
None,
Some(T),
}
use Option::*;

#[custom_mir(dialect = "runtime", phase = "optimized")]
fn set_discriminant(ptr: &mut Option<NonZeroI32>) {
mir! {
{
// We set the discriminant to `Some`, which is a NOP since this is the niched variant.
// However, the enum is actually encoding `None` currently! That's not good...
SetDiscriminant(*ptr, 1);
//~^ ERROR: trying to set discriminant of a Option<std::num::NonZero<i32>> to the niched variant, but the value does not match
Return()
}
}
}

pub fn main() {
let mut v = None;
set_discriminant(&mut v);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: trying to set discriminant of a Option<std::num::NonZero<i32>> to the niched variant, but the value does not match
--> $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC
|
LL | SetDiscriminant(*ptr, 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^ trying to set discriminant of a Option<std::num::NonZero<i32>> to the niched variant, but the value does not match
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `set_discriminant` at $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC
note: inside `main`
--> $DIR/enum-set-discriminant-niche-variant-wrong.rs:LL:CC
|
LL | set_discriminant(&mut v);
| ^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_move

fn main::{closure#0}::{closure#0}(_1: {coroutine-closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
fn main::{closure#0}::{closure#0}(_1: {async closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10};

bb0: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_move

fn main::{closure#0}::{closure#0}(_1: {coroutine-closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
fn main::{closure#0}::{closure#0}(_1: {async closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10};

bb0: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_mut

fn main::{closure#0}::{closure#0}(_1: &mut {coroutine-closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
fn main::{closure#0}::{closure#0}(_1: &mut {async closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
debug a => _2;
debug b => ((*_1).0: i32);
let mut _0: {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_mut

fn main::{closure#0}::{closure#0}(_1: &mut {coroutine-closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
fn main::{closure#0}::{closure#0}(_1: &mut {async closure@$DIR/async_closure_shims.rs:39:33: 39:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10} {
debug a => _2;
debug b => ((*_1).0: i32);
let mut _0: {async closure body@$DIR/async_closure_shims.rs:39:53: 42:10};
Expand Down
Loading