uses: actions/checkout@v1
with:
fetch-depth: 2
- - name: configure GitHub Actions to kill the build when outdated
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
- with:
- github_token: "${{ secrets.github_token }}"
- if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try'"
- name: configure the PR in which the error message will be posted
run: "echo \"[CI_PR_NUMBER=$num]\""
env:
- name: decide whether to skip this job
run: src/ci/scripts/should-skip-this.sh
if: success() && !env.SKIP_JOB
+ - name: configure GitHub Actions to kill the build when outdated
+ uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && !env.RUST_CI_TEMP_SKIP_CANCEL_OUTDATED"
- name: collect CPU statistics
run: src/ci/scripts/collect-cpu-stats.sh
if: success() && !env.SKIP_JOB
uses: actions/checkout@v1
with:
fetch-depth: 2
- - name: configure GitHub Actions to kill the build when outdated
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
- with:
- github_token: "${{ secrets.github_token }}"
- if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try'"
- name: configure the PR in which the error message will be posted
run: "echo \"[CI_PR_NUMBER=$num]\""
env:
- name: decide whether to skip this job
run: src/ci/scripts/should-skip-this.sh
if: success() && !env.SKIP_JOB
+ - name: configure GitHub Actions to kill the build when outdated
+ uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && !env.RUST_CI_TEMP_SKIP_CANCEL_OUTDATED"
- name: collect CPU statistics
run: src/ci/scripts/collect-cpu-stats.sh
if: success() && !env.SKIP_JOB
uses: actions/checkout@v1
with:
fetch-depth: 2
- - name: configure GitHub Actions to kill the build when outdated
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
- with:
- github_token: "${{ secrets.github_token }}"
- if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try'"
- name: configure the PR in which the error message will be posted
run: "echo \"[CI_PR_NUMBER=$num]\""
env:
- name: decide whether to skip this job
run: src/ci/scripts/should-skip-this.sh
if: success() && !env.SKIP_JOB
+ - name: configure GitHub Actions to kill the build when outdated
+ uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && !env.RUST_CI_TEMP_SKIP_CANCEL_OUTDATED"
- name: collect CPU statistics
run: src/ci/scripts/collect-cpu-stats.sh
if: success() && !env.SKIP_JOB
- name: dist-x86_64-apple
env:
SCRIPT: "./x.py dist"
- RUST_CONFIGURE_ARGS: "--target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc"
+ RUST_CONFIGURE_ARGS: "--target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false"
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
DIST_REQUIRE_ALL_TOOLS: 1
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
os: macos-latest
- name: dist-x86_64-apple-alt
env:
SCRIPT: "./x.py dist"
- RUST_CONFIGURE_ARGS: "--enable-extended --enable-profiler --set rust.jemalloc"
+ RUST_CONFIGURE_ARGS: "--enable-extended --enable-profiler --set rust.jemalloc --set llvm.ninja=false"
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
os: macos-latest
- name: x86_64-apple
env:
SCRIPT: "./x.py --stage 2 test"
- RUST_CONFIGURE_ARGS: "--build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc"
+ RUST_CONFIGURE_ARGS: "--build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false"
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.8
MACOSX_STD_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
os: macos-latest
timeout-minutes: 600
runs-on: "${{ matrix.os }}"
uses: actions/checkout@v1
with:
fetch-depth: 2
- - name: configure GitHub Actions to kill the build when outdated
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
- with:
- github_token: "${{ secrets.github_token }}"
- if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try'"
- name: configure the PR in which the error message will be posted
run: "echo \"[CI_PR_NUMBER=$num]\""
env:
- name: decide whether to skip this job
run: src/ci/scripts/should-skip-this.sh
if: success() && !env.SKIP_JOB
+ - name: configure GitHub Actions to kill the build when outdated
+ uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ if: "success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && !env.RUST_CI_TEMP_SKIP_CANCEL_OUTDATED"
- name: collect CPU statistics
run: src/ci/scripts/collect-cpu-stats.sh
if: success() && !env.SKIP_JOB
name = "cargo-miri"
version = "0.1.0"
dependencies = [
- "cargo_metadata 0.9.1",
+ "cargo_metadata 0.11.1",
"directories",
"rustc-workspace-hack",
"rustc_version",
[[package]]
name = "cargo_metadata"
-version = "0.9.1"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202"
+checksum = "89fec17b16f1ac67908af82e47d0a90a7afd0e1827b181cd77504323d3263d35"
dependencies = [
- "semver 0.9.0",
+ "semver 0.10.0",
"serde",
- "serde_derive",
"serde_json",
]
name = "clippy"
version = "0.0.212"
dependencies = [
- "cargo_metadata 0.9.1",
+ "cargo_metadata 0.11.1",
"clippy-mini-macro-test",
"clippy_lints",
"compiletest_rs",
"lazy_static",
"rustc-workspace-hack",
"rustc_tools_util 0.2.0",
- "semver 0.9.0",
+ "semver 0.10.0",
"serde",
"tempfile",
"tester",
name = "clippy_lints"
version = "0.0.212"
dependencies = [
- "cargo_metadata 0.9.1",
+ "cargo_metadata 0.11.1",
"if_chain",
"itertools 0.9.0",
"lazy_static",
"quine-mc_cluskey",
"quote",
"regex-syntax",
- "semver 0.9.0",
+ "semver 0.10.0",
"serde",
"smallvec 1.4.2",
"syn",
[[package]]
name = "compiler_builtins"
-version = "0.1.32"
+version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bc4ac2c824d2bfc612cba57708198547e9a26943af0632aff033e0693074d5c"
+checksum = "e3fcd8aba10d17504c87ef12d4f62ef404c6a4703d16682a9eb5543e6cf24455"
dependencies = [
"cc",
"rustc-std-workspace-core",
"yaml-rust 0.4.4",
]
+[[package]]
+name = "expect-test"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e383741ea1982866572109d1a8c807bd36aad91fca701489fdca56ef92b3b8"
+dependencies = [
+ "difference",
+ "once_cell",
+]
+
[[package]]
name = "failure"
version = "0.1.8"
name = "miri"
version = "0.1.0"
dependencies = [
- "byteorder",
"colored",
"compiletest_rs",
"env_logger 0.7.1",
version = "0.0.0"
dependencies = [
"bitflags",
- "flate2",
"libc",
"measureme",
"rustc-demangle",
"rustc_span",
"rustc_target",
"smallvec 1.4.2",
+ "snap",
"tracing",
]
name = "rustc_lexer"
version = "0.1.0"
dependencies = [
+ "expect-test",
"unicode-xid",
]
name = "rustc_metadata"
version = "0.0.0"
dependencies = [
- "flate2",
"libc",
"memmap",
"rustc_ast",
"rustc_span",
"rustc_target",
"smallvec 1.4.2",
+ "snap",
"stable_deref_trait",
"tracing",
"winapi 0.3.9",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
+[[package]]
+name = "snap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da73c8f77aebc0e40c300b93f0a5f1bece7a248a36eee287d4e095f35c7b7d6e"
+
[[package]]
name = "socket2"
version = "0.3.12"
name = "tidy"
version = "0.1.0"
dependencies = [
- "cargo_metadata 0.9.1",
+ "cargo_metadata 0.11.1",
"lazy_static",
"regex",
"walkdir",
* `python` 3 or 2.7
* GNU `make` 3.81 or later
* `cmake` 3.4.3 or later
+ * `ninja`
* `curl`
* `git`
* `ssl` which comes in `libssl-dev` or `openssl-devel`
-------------------
- [The target configuration option `abi_blacklist` has been renamed
to `unsupported_abis`.][74150] The old name will still continue to work.
-- [Rustc will now warn if you have a C-like enum that implements `Drop`.][72331]
+- [Rustc will now warn if you cast a C-like enum that implements `Drop`.][72331]
This was previously accepted but will become a hard error in a future release.
- [Rustc will fail to compile if you have a struct with
`#[repr(i128)]` or `#[repr(u128)]`.][74109] This representation is currently only
- [Rustc now correctly relates the lifetime of an existential associated
type.][71896] This fixes some edge cases where `rustc` would erroneously allow
you to pass a shorter lifetime than expected.
-
+- [Rustc now dynamically links to `libz` (also called `zlib`) on Linux.][74420]
+ The library will need to be installed for `rustc` to work, even though we
+ expect it to be already available on most systems.
+- [Tests annotated with `#[should_panic]` are broken on ARMv7 while running
+ under QEMU.][74820]
+- [Pretty printing of some tokens in procedural macros changed.][75453] The
+ exact output returned by rustc's pretty printing is an unstable
+ implementation detail: we recommend any macro relying on it to switch to a
+ more robust parsing system.
+
+[75453]: https://github.com/rust-lang/rust/issues/75453/
+[74820]: https://github.com/rust-lang/rust/issues/74820/
+[74420]: https://github.com/rust-lang/rust/issues/74420/
[74109]: https://github.com/rust-lang/rust/pull/74109/
[74150]: https://github.com/rust-lang/rust/pull/74150/
[73862]: https://github.com/rust-lang/rust/pull/73862/
# dynamic version to be available.
#static-libstdcpp = false
-# Tell the LLVM build system to use Ninja instead of the platform default for
-# the generated build system. This can sometimes be faster than make, for
-# example.
-#ninja = false
+# Whether to use Ninja to build LLVM. This runs much faster than make.
+#ninja = true
# LLVM targets to build support for.
# Note: this is NOT related to Rust compilation targets. However, as Rust is
#![stable(feature = "alloc_module", since = "1.28.0")]
use core::intrinsics::{self, min_align_of_val, size_of_val};
-use core::ptr::{NonNull, Unique};
+use core::ptr::{self, NonNull, Unique};
#[stable(feature = "alloc_module", since = "1.28.0")]
#[doc(inline)]
unsafe fn grow_impl(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
zeroed: bool,
) -> Result<NonNull<[u8]>, AllocErr> {
debug_assert!(
- new_size >= layout.size(),
- "`new_size` must be greater than or equal to `layout.size()`"
+ new_layout.size() >= old_layout.size(),
+ "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
- match layout.size() {
- // SAFETY: the caller must ensure that the `new_size` does not overflow.
- // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid for a Layout.
- 0 => unsafe {
- let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
- self.alloc_impl(new_layout, zeroed)
- },
+ match old_layout.size() {
+ 0 => self.alloc_impl(new_layout, zeroed),
// SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
// as required by safety conditions. Other conditions must be upheld by the caller
- old_size => unsafe {
- // `realloc` probably checks for `new_size >= size` or something similar.
- intrinsics::assume(new_size >= layout.size());
+ old_size if old_layout.align() == new_layout.align() => unsafe {
+ let new_size = new_layout.size();
+
+ // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
+ intrinsics::assume(new_size >= old_layout.size());
- let raw_ptr = realloc(ptr.as_ptr(), layout, new_size);
+ let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
if zeroed {
raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
}
Ok(NonNull::slice_from_raw_parts(ptr, new_size))
},
+
+ // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
+ // both the old and new memory allocation are valid for reads and writes for `old_size`
+ // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
+ // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
+ // for `dealloc` must be upheld by the caller.
+ old_size => unsafe {
+ let new_ptr = self.alloc_impl(new_layout, zeroed)?;
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), old_size);
+ self.dealloc(ptr, old_layout);
+ Ok(new_ptr)
+ },
}
}
}
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: all conditions must be upheld by the caller
- unsafe { self.grow_impl(ptr, layout, new_size, false) }
+ unsafe { self.grow_impl(ptr, old_layout, new_layout, false) }
}
#[inline]
unsafe fn grow_zeroed(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: all conditions must be upheld by the caller
- unsafe { self.grow_impl(ptr, layout, new_size, true) }
+ unsafe { self.grow_impl(ptr, old_layout, new_layout, true) }
}
#[inline]
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
debug_assert!(
- new_size <= layout.size(),
- "`new_size` must be smaller than or equal to `layout.size()`"
+ new_layout.size() <= old_layout.size(),
+ "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
);
- match new_size {
+ match new_layout.size() {
// SAFETY: conditions must be upheld by the caller
0 => unsafe {
- self.dealloc(ptr, layout);
- Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0))
+ self.dealloc(ptr, old_layout);
+ Ok(NonNull::slice_from_raw_parts(new_layout.dangling(), 0))
},
// SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
- new_size => unsafe {
- // `realloc` probably checks for `new_size <= size` or something similar.
- intrinsics::assume(new_size <= layout.size());
+ new_size if old_layout.align() == new_layout.align() => unsafe {
+ // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
+ intrinsics::assume(new_size <= old_layout.size());
- let raw_ptr = realloc(ptr.as_ptr(), layout, new_size);
+ let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
Ok(NonNull::slice_from_raw_parts(ptr, new_size))
},
+
+ // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
+ // both the old and new memory allocation are valid for reads and writes for `new_size`
+ // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
+ // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
+ // for `dealloc` must be upheld by the caller.
+ new_size => unsafe {
+ let new_ptr = self.alloc(new_layout)?;
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), new_size);
+ self.dealloc(ptr, old_layout);
+ Ok(new_ptr)
+ },
}
}
}
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
match Global.alloc(layout) {
- Ok(ptr) => ptr.as_non_null_ptr().as_ptr(),
+ Ok(ptr) => ptr.as_mut_ptr(),
Err(_) => handle_alloc_error(layout),
}
}
use core::alloc::LayoutErr;
use core::cmp;
+use core::intrinsics;
use core::mem::{self, ManuallyDrop, MaybeUninit};
use core::ops::Drop;
use core::ptr::{NonNull, Unique};
let new_size = amount * mem::size_of::<T>();
let ptr = unsafe {
- self.alloc.shrink(ptr, layout, new_size).map_err(|_| TryReserveError::AllocError {
- layout: Layout::from_size_align_unchecked(new_size, layout.align()),
+ let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
+ self.alloc.shrink(ptr, layout, new_layout).map_err(|_| TryReserveError::AllocError {
+ layout: new_layout,
non_exhaustive: (),
})?
};
let memory = if let Some((ptr, old_layout)) = current_memory {
debug_assert_eq!(old_layout.align(), new_layout.align());
- unsafe { alloc.grow(ptr, old_layout, new_layout.size()) }
+ unsafe {
+ // The allocator checks for alignment equality
+ intrinsics::assume(old_layout.align() == new_layout.align());
+ alloc.grow(ptr, old_layout, new_layout)
+ }
} else {
alloc.alloc(new_layout)
- }
- .map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?;
+ };
- Ok(memory)
+ memory.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })
}
unsafe impl<#[may_dangle] T, A: AllocRef> Drop for RawVec<T, A> {
//!
//! # Examples
//!
-//! You can explicitly create a [`Vec<T>`] with [`new`]:
+//! You can explicitly create a [`Vec`] with [`Vec::new`]:
//!
//! ```
//! let v: Vec<i32> = Vec::new();
//! v[1] = v[1] + 5;
//! ```
//!
-//! [`Vec<T>`]: Vec
-//! [`new`]: Vec::new
//! [`push`]: Vec::push
#![stable(feature = "rust1", since = "1.0.0")]
/// If `new_len` is less than `len`, the `Vec` is simply truncated.
///
/// This method uses a closure to create new values on every push. If
- /// you'd rather [`Clone`] a given value, use [`resize`]. If you want
- /// to use the [`Default`] trait to generate values, you can pass
- /// [`Default::default()`] as the second argument.
+ /// you'd rather [`Clone`] a given value, use [`Vec::resize`]. If you
+ /// want to use the [`Default`] trait to generate values, you can
+ /// pass [`Default::default`] as the second argument.
///
/// # Examples
///
/// vec.resize_with(4, || { p *= 2; p });
/// assert_eq!(vec, [2, 4, 8, 16]);
/// ```
- ///
- /// [`resize`]: Vec::resize
#[stable(feature = "vec_resize_with", since = "1.33.0")]
pub fn resize_with<F>(&mut self, new_len: usize, f: F)
where
/// Attempts to extend the memory block.
///
/// Returns a new [`NonNull<[u8]>`] containing a pointer and the actual size of the allocated
- /// memory. The pointer is suitable for holding data described by a new layout with `layout`’s
- /// alignment and a size given by `new_size`. To accomplish this, the allocator may extend the
- /// allocation referenced by `ptr` to fit the new layout.
+ /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
+ /// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout.
///
/// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
/// transferred to this allocator. The memory may or may not have been freed, and should be
///
/// # Safety
///
- /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator,
- /// * `layout` must [*fit*] that block of memory (The `new_size` argument need not fit it.),
- /// * `new_size` must be greater than or equal to `layout.size()`, and
- /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must not overflow
- /// (i.e., the rounded value must be less than or equal to `usize::MAX`).
+ /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
+ /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
+ /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*fit*]: #memory-fitting
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
- let size = layout.size();
debug_assert!(
- new_size >= size,
- "`new_size` must be greater than or equal to `layout.size()`"
+ new_layout.size() >= old_layout.size(),
+ "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
- // SAFETY: the caller must ensure that the `new_size` does not overflow.
- // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid for a Layout.
- let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let new_ptr = self.alloc(new_layout)?;
- // SAFETY: because `new_size` must be greater than or equal to `size`, both the old and new
- // memory allocation are valid for reads and writes for `size` bytes. Also, because the old
- // allocation wasn't yet deallocated, it cannot overlap `new_ptr`. Thus, the call to
- // `copy_nonoverlapping` is safe.
- // The safety contract for `dealloc` must be upheld by the caller.
+ // SAFETY: because `new_layout.size()` must be greater than or equal to
+ // `old_layout.size()`, both the old and new memory allocation are valid for reads and
+ // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
+ // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
+ // safe. The safety contract for `dealloc` must be upheld by the caller.
unsafe {
- ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), size);
- self.dealloc(ptr, layout);
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), old_layout.size());
+ self.dealloc(ptr, old_layout);
}
Ok(new_ptr)
///
/// The memory block will contain the following contents after a successful call to
/// `grow_zeroed`:
- /// * Bytes `0..layout.size()` are preserved from the original allocation.
- /// * Bytes `layout.size()..old_size` will either be preserved or zeroed, depending on the
- /// allocator implementation. `old_size` refers to the size of the memory block prior to
- /// the `grow_zeroed` call, which may be larger than the size that was originally requested
- /// when it was allocated.
+ /// * Bytes `0..old_layout.size()` are preserved from the original allocation.
+ /// * Bytes `old_layout.size()..old_size` will either be preserved or zeroed, depending on
+ /// the allocator implementation. `old_size` refers to the size of the memory block prior
+ /// to the `grow_zeroed` call, which may be larger than the size that was originally
+ /// requested when it was allocated.
/// * Bytes `old_size..new_size` are zeroed. `new_size` refers to the size of the memory
- /// block returned by the `grow` call.
+ /// block returned by the `grow_zeroed` call.
///
/// # Safety
///
- /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator,
- /// * `layout` must [*fit*] that block of memory (The `new_size` argument need not fit it.),
- /// * `new_size` must be greater than or equal to `layout.size()`, and
- /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must not overflow
- /// (i.e., the rounded value must be less than or equal to `usize::MAX`).
+ /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
+ /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
+ /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*fit*]: #memory-fitting
unsafe fn grow_zeroed(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
- let size = layout.size();
debug_assert!(
- new_size >= size,
- "`new_size` must be greater than or equal to `layout.size()`"
+ new_layout.size() >= old_layout.size(),
+ "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
- // SAFETY: the caller must ensure that the `new_size` does not overflow.
- // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid for a Layout.
- let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let new_ptr = self.alloc_zeroed(new_layout)?;
- // SAFETY: because `new_size` must be greater than or equal to `size`, both the old and new
- // memory allocation are valid for reads and writes for `size` bytes. Also, because the old
- // allocation wasn't yet deallocated, it cannot overlap `new_ptr`. Thus, the call to
- // `copy_nonoverlapping` is safe.
- // The safety contract for `dealloc` must be upheld by the caller.
+ // SAFETY: because `new_layout.size()` must be greater than or equal to
+ // `old_layout.size()`, both the old and new memory allocation are valid for reads and
+ // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
+ // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
+ // safe. The safety contract for `dealloc` must be upheld by the caller.
unsafe {
- ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), size);
- self.dealloc(ptr, layout);
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), old_layout.size());
+ self.dealloc(ptr, old_layout);
}
Ok(new_ptr)
/// Attempts to shrink the memory block.
///
/// Returns a new [`NonNull<[u8]>`] containing a pointer and the actual size of the allocated
- /// memory. The pointer is suitable for holding data described by a new layout with `layout`’s
- /// alignment and a size given by `new_size`. To accomplish this, the allocator may shrink the
- /// allocation referenced by `ptr` to fit the new layout.
+ /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
+ /// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout.
///
/// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
/// transferred to this allocator. The memory may or may not have been freed, and should be
///
/// # Safety
///
- /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator,
- /// * `layout` must [*fit*] that block of memory (The `new_size` argument need not fit it.), and
- /// * `new_size` must be smaller than or equal to `layout.size()`.
+ /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
+ /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
+ /// * `new_layout.size()` must be smaller than or equal to `old_layout.size()`.
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*fit*]: #memory-fitting
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
- let size = layout.size();
debug_assert!(
- new_size <= size,
- "`new_size` must be smaller than or equal to `layout.size()`"
+ new_layout.size() <= old_layout.size(),
+ "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
);
- // SAFETY: the caller must ensure that the `new_size` does not overflow.
- // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid for a Layout.
- let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let new_ptr = self.alloc(new_layout)?;
- // SAFETY: because `new_size` must be lower than or equal to `size`, both the old and new
- // memory allocation are valid for reads and writes for `new_size` bytes. Also, because the
- // old allocation wasn't yet deallocated, it cannot overlap `new_ptr`. Thus, the call to
- // `copy_nonoverlapping` is safe.
- // The safety contract for `dealloc` must be upheld by the caller.
+ // SAFETY: because `new_layout.size()` must be lower than or equal to
+ // `old_layout.size()`, both the old and new memory allocation are valid for reads and
+ // writes for `new_layout.size()` bytes. Also, because the old allocation wasn't yet
+ // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
+ // safe. The safety contract for `dealloc` must be upheld by the caller.
unsafe {
- ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), size);
- self.dealloc(ptr, layout);
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), new_layout.size());
+ self.dealloc(ptr, old_layout);
}
Ok(new_ptr)
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: the safety contract must be upheld by the caller
- unsafe { (**self).grow(ptr, layout, new_size) }
+ unsafe { (**self).grow(ptr, old_layout, new_layout) }
}
#[inline]
unsafe fn grow_zeroed(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: the safety contract must be upheld by the caller
- unsafe { (**self).grow_zeroed(ptr, layout, new_size) }
+ unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) }
}
#[inline]
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: the safety contract must be upheld by the caller
- unsafe { (**self).shrink(ptr, layout, new_size) }
+ unsafe { (**self).shrink(ptr, old_layout, new_layout) }
}
}
array_impl_default! {32, T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T}
-#[cfg(not(bootstrap))]
#[lang = "array"]
impl<T, const N: usize> [T; N] {
/// Returns an array of the same size as `self`, with function `f` applied to each element
))]
pub trait From<T>: Sized {
/// Performs the conversion.
- #[cfg_attr(not(bootstrap), lang = "from")]
+ #[lang = "from"]
#[stable(feature = "rust1", since = "1.0.0")]
fn from(_: T) -> Self;
}
/// When using a future, you generally won't call `poll` directly, but instead
/// `.await` the value.
///
-/// [`Waker`]: ../task/struct.Waker.html
+/// [`Waker`]: crate::task::Waker
#[doc(spotlight)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[stable(feature = "futures_api", since = "1.36.0")]
/// (memory corruption, incorrect use of `unsafe` functions, or the like),
/// regardless of the future's state.
///
- /// [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending
- /// [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready
- /// [`Context`]: ../task/struct.Context.html
- /// [`Waker`]: ../task/struct.Waker.html
- /// [`Waker::wake`]: ../task/struct.Waker.html#method.wake
- #[cfg_attr(not(bootstrap), lang = "poll")]
+ /// [`Poll::Ready(val)`]: Poll::Ready
+ /// [`Waker`]: crate::task::Waker
+ /// [`Waker::wake`]: crate::task::Waker::wake
+ #[lang = "poll"]
#[stable(feature = "futures_api", since = "1.36.0")]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
/// This function returns a `GenFuture` underneath, but hides it in `impl Trait` to give
/// better error messages (`impl Future` rather than `GenFuture<[closure.....]>`).
// This is `const` to avoid extra errors after we recover from `const async fn`
-#[cfg_attr(not(bootstrap), lang = "from_generator")]
+#[lang = "from_generator"]
#[doc(hidden)]
#[unstable(feature = "gen_future", issue = "50547")]
#[inline]
GenFuture(gen)
}
-#[cfg_attr(not(bootstrap), lang = "get_context")]
+#[lang = "get_context"]
#[doc(hidden)]
#[unstable(feature = "gen_future", issue = "50547")]
#[inline]
//! If you need more control over how a value is hashed, you need to implement
//! the [`Hash`] trait:
//!
-//! [`Hash`]: trait.Hash.html
-//!
//! ```rust
//! use std::collections::hash_map::DefaultHasher;
//! use std::hash::{Hash, Hasher};
/// Thankfully, you won't need to worry about upholding this property when
/// deriving both [`Eq`] and `Hash` with `#[derive(PartialEq, Eq, Hash)]`.
///
-/// [`Eq`]: ../../std/cmp/trait.Eq.html
-/// [`Hasher`]: trait.Hasher.html
/// [`HashMap`]: ../../std/collections/struct.HashMap.html
/// [`HashSet`]: ../../std/collections/struct.HashSet.html
-/// [`hash`]: #tymethod.hash
+/// [`hash`]: Hash::hash
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Hash {
/// Feeds this value into the given [`Hasher`].
/// 7920.hash(&mut hasher);
/// println!("Hash is {:x}!", hasher.finish());
/// ```
- ///
- /// [`Hasher`]: trait.Hasher.html
#[stable(feature = "rust1", since = "1.0.0")]
fn hash<H: Hasher>(&self, state: &mut H);
/// Hash::hash_slice(&numbers, &mut hasher);
/// println!("Hash is {:x}!", hasher.finish());
/// ```
- ///
- /// [`Hasher`]: trait.Hasher.html
#[stable(feature = "hash_slice", since = "1.3.0")]
fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
where
/// println!("Hash is {:x}!", hasher.finish());
/// ```
///
-/// [`Hash`]: trait.Hash.html
-/// [`finish`]: #tymethod.finish
-/// [`write`]: #tymethod.write
-/// [`write_u8`]: #method.write_u8
+/// [`finish`]: Hasher::finish
+/// [`write`]: Hasher::write
+/// [`write_u8`]: Hasher::write_u8
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Hasher {
/// Returns the hash value for the values written so far.
/// println!("Hash is {:x}!", hasher.finish());
/// ```
///
- /// [`write`]: #tymethod.write
+ /// [`write`]: Hasher::write
#[stable(feature = "rust1", since = "1.0.0")]
fn finish(&self) -> u64;
/// assert_eq!(hasher_1.finish(), hasher_2.finish());
/// ```
///
-/// [`build_hasher`]: #tymethod.build_hasher
-/// [`Hasher`]: trait.Hasher.html
+/// [`build_hasher`]: BuildHasher::build_hasher
/// [`HashMap`]: ../../std/collections/struct.HashMap.html
#[stable(since = "1.7.0", feature = "build_hasher")]
pub trait BuildHasher {
/// let s = RandomState::new();
/// let new_s = s.build_hasher();
/// ```
- ///
- /// [`Hasher`]: trait.Hasher.html
#[stable(since = "1.7.0", feature = "build_hasher")]
fn build_hasher(&self) -> Self::Hasher;
}
/// defined.
///
/// Any `BuildHasherDefault` is [zero-sized]. It can be created with
-/// [`default`][method.Default]. When using `BuildHasherDefault` with [`HashMap`] or
+/// [`default`][method.default]. When using `BuildHasherDefault` with [`HashMap`] or
/// [`HashSet`], this doesn't need to be done, since they implement appropriate
/// [`Default`] instances themselves.
///
/// let hash_map = HashMap::<u32, u32, MyBuildHasher>::default();
/// ```
///
-/// [`BuildHasher`]: trait.BuildHasher.html
-/// [`Default`]: ../default/trait.Default.html
-/// [method.default]: #method.default
-/// [`Hasher`]: trait.Hasher.html
+/// [method.default]: BuildHasherDefault::default
/// [`HashMap`]: ../../std/collections/struct.HashMap.html
/// [`HashSet`]: ../../std/collections/struct.HashSet.html
/// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts
/// [`std::convert::identity`]: https://doc.rust-lang.org/core/convert/fn.identity.html
///
/// Unlike [`std::convert::identity`], a Rust compiler is encouraged to assume that `black_box` can
-/// use `x` in any possible valid way that Rust code is allowed to without introducing undefined
+/// use `dummy` in any possible valid way that Rust code is allowed to without introducing undefined
/// behavior in the calling code. This property makes `black_box` useful for writing code in which
/// certain optimizations are not desired, such as benchmarks.
///
/// assert_eq!(Some(3), iter.next());
/// assert_eq!(None, iter.next());
/// ```
- #[cfg_attr(not(bootstrap), lang = "into_iter")]
+ #[lang = "into_iter"]
#[stable(feature = "rust1", since = "1.0.0")]
fn into_iter(self) -> Self::IntoIter;
}
/// assert_eq!(None, iter.next());
/// assert_eq!(None, iter.next());
/// ```
- #[cfg_attr(not(bootstrap), lang = "next")]
+ #[lang = "next"]
#[stable(feature = "rust1", since = "1.0.0")]
fn next(&mut self) -> Option<Self::Item>;
#![feature(custom_inner_attributes)]
#![feature(decl_macro)]
#![feature(doc_cfg)]
-#![cfg_attr(not(bootstrap), feature(doc_spotlight))]
+#![feature(doc_spotlight)]
#![feature(duration_consts_2)]
#![feature(extern_types)]
#![feature(fundamental)]
/// This macro accepts a format string, a list of arguments, and a 'writer'. Arguments will be
/// formatted according to the specified format string and the result will be passed to the writer.
/// The writer may be any value with a `write_fmt` method; generally this comes from an
-/// implementation of either the [`std::fmt::Write`] or the [`std::io::Write`] trait. The macro
-/// returns whatever the `write_fmt` method returns; commonly a [`std::fmt::Result`], or an
+/// implementation of either the [`fmt::Write`] or the [`io::Write`] trait. The macro
+/// returns whatever the `write_fmt` method returns; commonly a [`fmt::Result`], or an
/// [`io::Result`].
///
/// See [`std::fmt`] for more information on the format string syntax.
///
/// [`std::fmt`]: crate::fmt
-/// [`std::fmt::Write`]: crate::fmt::Write
-/// [`std::io::Write`]: ../std/io/trait.Write.html
-/// [`std::fmt::Result`]: crate::fmt::Result
+/// [`fmt::Write`]: crate::fmt::Write
+/// [`io::Write`]: ../std/io/trait.Write.html
+/// [`fmt::Result`]: crate::fmt::Result
/// [`io::Result`]: ../std/io/type.Result.html
///
/// # Examples
an unrecoverable state.
This macro is the perfect way to assert conditions in example code and in
-tests. `panic!` is closely tied with the `unwrap` method of both [`Option`]
-and [`Result`][runwrap] enums. Both implementations call `panic!` when they are set
-to None or Err variants.
+tests. `panic!` is closely tied with the `unwrap` method of both
+[`Option`][ounwrap] and [`Result`][runwrap] enums. Both implementations call
+`panic!` when they are set to [`None`] or [`Err`] variants.
This macro is used to inject panic into a Rust thread, causing the thread to
-panic entirely. Each thread's panic can be reaped as the `Box<Any>` type,
+panic entirely. Each thread's panic can be reaped as the [`Box`]`<`[`Any`]`>` type,
and the single-argument form of the `panic!` macro will be the value which
is transmitted.
See also the macro [`compile_error!`], for raising errors during compilation.
-[runwrap]: ../std/result/enum.Result.html#method.unwrap
-[`Option`]: ../std/option/enum.Option.html#method.unwrap
-[`Result`]: ../std/result/enum.Result.html
+[ounwrap]: Option::unwrap
+[runwrap]: Result::unwrap
+[`Box`]: ../std/boxed/struct.Box.html
+[`Any`]: crate::any::Any
[`format!`]: ../std/macro.format.html
-[`compile_error!`]: ../std/macro.compile_error.html
[book]: ../book/ch09-00-error-handling.html
# Current implementation
pub trait DiscriminantKind {
/// The type of the discriminant, which must satisfy the trait
/// bounds required by `mem::Discriminant`.
- #[cfg_attr(not(bootstrap), lang = "discriminant_type")]
+ #[lang = "discriminant_type"]
type Discriminant: Clone + Copy + Debug + Eq + PartialEq + Hash + Send + Sync + Unpin;
}
/// Types that can be safely moved after being pinned.
///
-/// Since Rust itself has no notion of immovable types, and considers moves
-/// (e.g., through assignment or [`mem::replace`]) to always be safe,
-/// this trait cannot prevent types from moving by itself.
+/// Rust itself has no notion of immovable types, and considers moves (e.g.,
+/// through assignment or [`mem::replace`]) to always be safe.
///
-/// Instead it is used to prevent moves through the type system,
-/// by controlling the behavior of pointers `P` wrapped in the [`Pin<P>`] wrapper,
-/// which "pin" the type in place by not allowing it to be moved out of them.
-/// See the [`pin module`] documentation for more information on pinning.
+/// The [`Pin`][Pin] type is used instead to prevent moves through the type
+/// system. Pointers `P<T>` wrapped in the [`Pin<P<T>>`][Pin] wrapper can't be
+/// moved out of. See the [`pin module`] documentation for more information on
+/// pinning.
///
-/// Implementing this trait lifts the restrictions of pinning off a type,
-/// which then allows it to move out with functions such as [`mem::replace`].
+/// Implementing the `Unpin` trait for `T` lifts the restrictions of pinning off
+/// the type, which then allows moving `T` out of [`Pin<P<T>>`][Pin] with
+/// functions such as [`mem::replace`].
///
/// `Unpin` has no consequence at all for non-pinned data. In particular,
/// [`mem::replace`] happily moves `!Unpin` data (it works for any `&mut T`, not
-/// just when `T: Unpin`). However, you cannot use
-/// [`mem::replace`] on data wrapped inside a [`Pin<P>`] because you cannot get the
-/// `&mut T` you need for that, and *that* is what makes this system work.
+/// just when `T: Unpin`). However, you cannot use [`mem::replace`] on data
+/// wrapped inside a [`Pin<P<T>>`][Pin] because you cannot get the `&mut T` you
+/// need for that, and *that* is what makes this system work.
///
/// So this, for example, can only be done on types implementing `Unpin`:
///
/// This trait is automatically implemented for almost every type.
///
/// [`mem::replace`]: ../../std/mem/fn.replace.html
-/// [`Pin<P>`]: ../pin/struct.Pin.html
-/// [`pin module`]: ../../std/pin/index.html
+/// [Pin]: crate::pin::Pin
+/// [`pin module`]: crate::pin
#[stable(feature = "pin", since = "1.33.0")]
#[rustc_on_unimplemented(
on(_Self = "std::future::Future", note = "consider using `Box::pin`",),
///
/// ```rust
/// use std::mem::ManuallyDrop;
- /// ManuallyDrop::new(Box::new(()));
+ /// let mut x = ManuallyDrop::new(String::from("Hello World!"));
+ /// x.truncate(5); // You can still safely operate on the value
+ /// assert_eq!(*x, "Hello");
+ /// // But `Drop` will not be run here
/// ```
+ #[must_use = "if you don't need the wrapper, you can use `mem::forget` instead"]
#[stable(feature = "manually_drop", since = "1.20.0")]
#[rustc_const_stable(feature = "const_manually_drop", since = "1.36.0")]
#[inline(always)]
#[rustc_const_stable(feature = "const_forget", since = "1.46.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub const fn forget<T>(t: T) {
- ManuallyDrop::new(t);
+ let _ = ManuallyDrop::new(t);
}
/// Like [`forget`], but also accepts unsized values.
///
/// This error is used as the error type for the [`FromStr`] implementation
/// for [`f32`] and [`f64`].
-///
-/// [`FromStr`]: ../str/trait.FromStr.html
-/// [`f32`]: ../../std/primitive.f32.html
-/// [`f64`]: ../../std/primitive.f64.html
#[derive(Debug, Clone, PartialEq, Eq)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct ParseFloatError {
/// ```
///
/// [slicing index]: crate::slice::SliceIndex
-#[cfg_attr(not(bootstrap), lang = "RangeFull")]
+#[lang = "RangeFull"]
#[doc(alias = "..")]
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
/// assert_eq!(arr[1.. 3], [ 1,2 ]); // Range
/// assert_eq!(arr[1..=3], [ 1,2,3 ]);
/// ```
-#[cfg_attr(not(bootstrap), lang = "Range")]
+#[lang = "Range"]
#[doc(alias = "..")]
#[derive(Clone, Default, PartialEq, Eq, Hash)] // not Copy -- see #27186
#[stable(feature = "rust1", since = "1.0.0")]
/// # Examples
///
/// ```
- /// #![feature(range_is_empty)]
- ///
/// assert!(!(3..5).is_empty());
/// assert!( (3..3).is_empty());
/// assert!( (3..2).is_empty());
/// The range is empty if either side is incomparable:
///
/// ```
- /// #![feature(range_is_empty)]
- ///
/// assert!(!(3.0..5.0).is_empty());
/// assert!( (3.0..f32::NAN).is_empty());
/// assert!( (f32::NAN..5.0).is_empty());
/// ```
- #[unstable(feature = "range_is_empty", reason = "recently added", issue = "48111")]
+ #[stable(feature = "range_is_empty", since = "1.47.0")]
pub fn is_empty(&self) -> bool {
!(self.start < self.end)
}
/// assert_eq!(arr[1.. 3], [ 1,2 ]);
/// assert_eq!(arr[1..=3], [ 1,2,3 ]);
/// ```
-#[cfg_attr(not(bootstrap), lang = "RangeFrom")]
+#[lang = "RangeFrom"]
#[doc(alias = "..")]
#[derive(Clone, PartialEq, Eq, Hash)] // not Copy -- see #27186
#[stable(feature = "rust1", since = "1.0.0")]
/// ```
///
/// [slicing index]: crate::slice::SliceIndex
-#[cfg_attr(not(bootstrap), lang = "RangeTo")]
+#[lang = "RangeTo"]
#[doc(alias = "..")]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
/// assert_eq!(arr[1.. 3], [ 1,2 ]);
/// assert_eq!(arr[1..=3], [ 1,2,3 ]); // RangeInclusive
/// ```
-#[cfg_attr(not(bootstrap), lang = "RangeInclusive")]
+#[lang = "RangeInclusive"]
#[doc(alias = "..=")]
#[derive(Clone, PartialEq, Eq, Hash)] // not Copy -- see #27186
#[stable(feature = "inclusive_range", since = "1.26.0")]
///
/// assert_eq!(3..=5, RangeInclusive::new(3, 5));
/// ```
- #[cfg_attr(not(bootstrap), lang = "range_inclusive_new")]
+ #[lang = "range_inclusive_new"]
#[stable(feature = "inclusive_range_methods", since = "1.27.0")]
#[inline]
#[rustc_promotable]
/// # Examples
///
/// ```
- /// #![feature(range_is_empty)]
- ///
/// assert!(!(3..=5).is_empty());
/// assert!(!(3..=3).is_empty());
/// assert!( (3..=2).is_empty());
/// The range is empty if either side is incomparable:
///
/// ```
- /// #![feature(range_is_empty)]
- ///
/// assert!(!(3.0..=5.0).is_empty());
/// assert!( (3.0..=f32::NAN).is_empty());
/// assert!( (f32::NAN..=5.0).is_empty());
/// This method returns `true` after iteration has finished:
///
/// ```
- /// #![feature(range_is_empty)]
- ///
/// let mut r = 3..=5;
/// for _ in r.by_ref() {}
/// // Precise field values are unspecified here
/// assert!(r.is_empty());
/// ```
- #[unstable(feature = "range_is_empty", reason = "recently added", issue = "48111")]
+ #[stable(feature = "range_is_empty", since = "1.47.0")]
#[inline]
pub fn is_empty(&self) -> bool {
self.exhausted || !(self.start <= self.end)
/// ```
///
/// [slicing index]: crate::slice::SliceIndex
-#[cfg_attr(not(bootstrap), lang = "RangeToInclusive")]
+#[lang = "RangeToInclusive"]
#[doc(alias = "..=")]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[stable(feature = "inclusive_range", since = "1.26.0")]
/// in the return type of the enclosing scope (which must itself implement
/// `Try`). Specifically, the value `X::from_error(From::from(e))`
/// is returned, where `X` is the return type of the enclosing function.
- #[cfg_attr(not(bootstrap), lang = "into_result")]
+ #[lang = "into_result"]
#[unstable(feature = "try_trait", issue = "42327")]
fn into_result(self) -> Result<Self::Ok, Self::Error>;
/// Wrap an error value to construct the composite result. For example,
/// `Result::Err(x)` and `Result::from_error(x)` are equivalent.
- #[cfg_attr(not(bootstrap), lang = "from_error")]
+ #[lang = "from_error"]
#[unstable(feature = "try_trait", issue = "42327")]
fn from_error(v: Self::Error) -> Self;
/// Wrap an OK value to construct the composite result. For example,
/// `Result::Ok(x)` and `Result::from_ok(x)` are equivalent.
- #[cfg_attr(not(bootstrap), lang = "from_ok")]
+ #[lang = "from_ok"]
#[unstable(feature = "try_trait", issue = "42327")]
fn from_ok(v: Self::Ok) -> Self;
}
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option<T> {
/// No value
- #[cfg_attr(not(bootstrap), lang = "None")]
+ #[lang = "None"]
#[stable(feature = "rust1", since = "1.0.0")]
None,
/// Some value `T`
- #[cfg_attr(not(bootstrap), lang = "Some")]
+ #[lang = "Some"]
#[stable(feature = "rust1", since = "1.0.0")]
Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}
/// ```
///
/// [`mem::swap`]: crate::mem::swap
- #[cfg_attr(not(bootstrap), lang = "new_unchecked")]
+ #[lang = "new_unchecked"]
#[stable(feature = "pin", since = "1.33.0")]
#[inline(always)]
pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
/// # use std::mem::align_of;
/// # unsafe {
/// let x = [5u8, 6u8, 7u8, 8u8, 9u8];
- /// let ptr = &x[n] as *const u8;
+ /// let ptr = x.as_ptr().add(n) as *const u8;
/// let offset = ptr.align_offset(align_of::<u16>());
/// if offset < x.len() - n - 1 {
/// let u16_ptr = ptr.add(offset) as *const u16;
/// # use std::mem::align_of;
/// # unsafe {
/// let x = [5u8, 6u8, 7u8, 8u8, 9u8];
- /// let ptr = &x[n] as *const u8;
+ /// let ptr = x.as_ptr().add(n) as *const u8;
/// let offset = ptr.align_offset(align_of::<u16>());
/// if offset < x.len() - n - 1 {
/// let u16_ptr = ptr.add(offset) as *const u16;
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
/// Contains the success value
- #[cfg_attr(not(bootstrap), lang = "Ok")]
+ #[lang = "Ok"]
#[stable(feature = "rust1", since = "1.0.0")]
Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
/// Contains the error value
- #[cfg_attr(not(bootstrap), lang = "Err")]
+ #[lang = "Err"]
#[stable(feature = "rust1", since = "1.0.0")]
Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}
#[stable(feature = "futures_api", since = "1.36.0")]
pub enum Poll<T> {
/// Represents that a value is immediately ready.
- #[cfg_attr(not(bootstrap), lang = "Ready")]
+ #[lang = "Ready"]
#[stable(feature = "futures_api", since = "1.36.0")]
Ready(#[stable(feature = "futures_api", since = "1.36.0")] T),
/// When a function returns `Pending`, the function *must* also
/// ensure that the current task is scheduled to be awoken when
/// progress can be made.
- #[cfg_attr(not(bootstrap), lang = "Pending")]
+ #[lang = "Pending"]
#[stable(feature = "futures_api", since = "1.36.0")]
Pending,
}
#![feature(try_find)]
#![feature(is_sorted)]
#![feature(pattern)]
-#![feature(range_is_empty)]
#![feature(raw)]
#![feature(sort_internals)]
#![feature(slice_partition_at_index)]
#![feature(panic_runtime)]
#![feature(staged_api)]
#![feature(rustc_attrs)]
+#![feature(llvm_asm)]
use core::any::Any;
unreachable!()
}
-// "Leak" the payload and shim to the relevant abort on the platform in
-// question.
-//
-// For Unix we just use `abort` from libc as it'll trigger debuggers, core
-// dumps, etc, as one might expect. On Windows, however, the best option we have
-// is the `__fastfail` intrinsics, but that's unfortunately not defined in LLVM,
-// and the `RaiseFailFastException` function isn't available until Windows 7
-// which would break compat with XP. For now just use `intrinsics::abort` which
-// will kill us with an illegal instruction, which will do a good enough job for
-// now hopefully.
+// "Leak" the payload and shim to the relevant abort on the platform in question.
#[rustc_std_internal_symbol]
pub unsafe extern "C" fn __rust_start_panic(_payload: usize) -> u32 {
abort();
}
__rust_abort();
}
+ } else if #[cfg(all(windows, any(target_arch = "x86", target_arch = "x86_64")))] {
+ // On Windows, use the processor-specific __fastfail mechanism. In Windows 8
+ // and later, this will terminate the process immediately without running any
+ // in-process exception handlers. In earlier versions of Windows, this
+ // sequence of instructions will be treated as an access violation,
+ // terminating the process but without necessarily bypassing all exception
+ // handlers.
+ //
+ // https://docs.microsoft.com/en-us/cpp/intrinsics/fastfail
+ //
+ // Note: this is the same implementation as in libstd's `abort_internal`
+ unsafe fn abort() -> ! {
+ llvm_asm!("int $$0x29" :: "{ecx}"(7) ::: volatile); // 7 is FAST_FAIL_FATAL_APP_EXIT
+ core::intrinsics::unreachable();
+ }
} else {
unsafe fn abort() -> ! {
core::intrinsics::abort();
1 // `ExceptionContinueSearch`
}
+ // Similar to above, this corresponds to the `eh_catch_typeinfo` lang item
+ // that's only used on Emscripten currently.
+ //
+ // Since panics don't generate exceptions and foreign exceptions are
+ // currently UB with -C panic=abort (although this may be subject to
+ // change), any catch_unwind calls will never use this typeinfo.
+ #[rustc_std_internal_symbol]
+ #[allow(non_upper_case_globals)]
+ #[cfg(target_os = "emscripten")]
+ static rust_eh_catch_typeinfo: [usize; 2] = [0; 2];
+
// These two are called by our startup objects on i686-pc-windows-gnu, but
// they don't need to do anything so the bodies are nops.
#[rustc_std_internal_symbol]
pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm"));
-pub unsafe fn find_eh_action(
- lsda: *const u8,
- context: &EHContext<'_>,
- foreign_exception: bool,
-) -> Result<EHAction, ()> {
+pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result<EHAction, ()> {
if lsda.is_null() {
return Ok(EHAction::None);
}
return Ok(EHAction::None);
} else {
let lpad = lpad_base + cs_lpad;
- return Ok(interpret_cs_action(cs_action, lpad, foreign_exception));
+ return Ok(interpret_cs_action(cs_action, lpad));
}
}
}
// Can never have null landing pad for sjlj -- that would have
// been indicated by a -1 call site index.
let lpad = (cs_lpad + 1) as usize;
- return Ok(interpret_cs_action(cs_action, lpad, foreign_exception));
+ return Ok(interpret_cs_action(cs_action, lpad));
}
}
}
}
-fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction {
+fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction {
if cs_action == 0 {
// If cs_action is 0 then this is a cleanup (Drop::drop). We run these
// for both Rust panics and foreign exceptions.
EHAction::Cleanup(lpad)
- } else if foreign_exception {
- // catch_unwind should not catch foreign exceptions, only Rust panics.
- // Instead just continue unwinding.
- EHAction::None
} else {
// Stop unwinding Rust panics at catch_unwind.
EHAction::Catch(lpad)
use alloc::boxed::Box;
use core::any::Any;
+use core::intrinsics;
use core::mem;
use core::ptr;
+use core::sync::atomic::{AtomicBool, Ordering};
use libc::{self, c_int};
use unwind as uw;
};
struct Exception {
+ // This is necessary because C++ code can capture our execption with
+ // std::exception_ptr and rethrow it multiple times, possibly even in
+ // another thread.
+ caught: AtomicBool,
+
// This needs to be an Option because the object's lifetime follows C++
// semantics: when catch_unwind moves the Box out of the exception it must
// still leave the exception object in a valid state because its destructor
}
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
- assert!(!ptr.is_null());
- let adjusted_ptr = __cxa_begin_catch(ptr as *mut libc::c_void) as *mut Exception;
- let ex = (*adjusted_ptr).data.take();
+ // intrinsics::try actually gives us a pointer to this structure.
+ #[repr(C)]
+ struct CatchData {
+ ptr: *mut u8,
+ is_rust_panic: bool,
+ }
+ let catch_data = &*(ptr as *mut CatchData);
+
+ let adjusted_ptr = __cxa_begin_catch(catch_data.ptr as *mut libc::c_void) as *mut Exception;
+ let out = if catch_data.is_rust_panic {
+ let was_caught = (*adjusted_ptr).caught.swap(true, Ordering::SeqCst);
+ if was_caught {
+ // Since cleanup() isn't allowed to panic, we just abort instead.
+ intrinsics::abort();
+ }
+ (*adjusted_ptr).data.take().unwrap()
+ } else {
+ super::__rust_foreign_exception();
+ };
__cxa_end_catch();
- ex.unwrap()
+ out
}
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
if exception.is_null() {
return uw::_URC_FATAL_PHASE1_ERROR as u32;
}
- ptr::write(exception, Exception { data: Some(data) });
+ ptr::write(exception, Exception { caught: AtomicBool::new(false), data: Some(data) });
__cxa_throw(exception as *mut _, &EXCEPTION_TYPE_INFO, exception_cleanup);
}
-// On WASM and ARM, the destructor returns the pointer to the object.
-cfg_if::cfg_if! {
- if #[cfg(any(target_arch = "arm", target_arch = "wasm32"))] {
- type DestructorRet = *mut libc::c_void;
- } else {
- type DestructorRet = ();
- }
-}
-extern "C" fn exception_cleanup(ptr: *mut libc::c_void) -> DestructorRet {
+extern "C" fn exception_cleanup(ptr: *mut libc::c_void) -> *mut libc::c_void {
unsafe {
if let Some(b) = (ptr as *mut Exception).read().data {
drop(b);
super::__rust_drop_panic();
}
- #[cfg(any(target_arch = "arm", target_arch = "wasm32"))]
ptr
}
}
fn __cxa_throw(
thrown_exception: *mut libc::c_void,
tinfo: *const TypeInfo,
- dest: extern "C" fn(*mut libc::c_void) -> DestructorRet,
+ dest: extern "C" fn(*mut libc::c_void) -> *mut libc::c_void,
) -> !;
fn __gxx_personality_v0(
version: c_int,
}
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
- let exception = Box::from_raw(ptr as *mut Exception);
- exception.cause
+ let exception = ptr as *mut uw::_Unwind_Exception;
+ if (*exception).exception_class != rust_exception_class() {
+ uw::_Unwind_DeleteException(exception);
+ super::__rust_foreign_exception();
+ } else {
+ let exception = Box::from_raw(exception as *mut Exception);
+ exception.cause
+ }
}
// Rust's exception class identifier. This is used by personality routines to
// _Unwind_Context in our libunwind bindings and fetch the required data from there
// directly, bypassing DWARF compatibility functions.
- let exception_class = (*exception_object).exception_class;
- let foreign_exception = exception_class != rust_exception_class();
- let eh_action = match find_eh_action(context, foreign_exception) {
+ let eh_action = match find_eh_action(context) {
Ok(action) => action,
Err(_) => return uw::_URC_FAILURE,
};
// and indirectly on Windows x86_64 via SEH.
unsafe extern "C" fn rust_eh_personality_impl(version: c_int,
actions: uw::_Unwind_Action,
- exception_class: uw::_Unwind_Exception_Class,
+ _exception_class: uw::_Unwind_Exception_Class,
exception_object: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context)
-> uw::_Unwind_Reason_Code {
if version != 1 {
return uw::_URC_FATAL_PHASE1_ERROR;
}
- let foreign_exception = exception_class != rust_exception_class();
- let eh_action = match find_eh_action(context, foreign_exception) {
+ let eh_action = match find_eh_action(context) {
Ok(action) => action,
Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
};
}
}
-unsafe fn find_eh_action(
- context: *mut uw::_Unwind_Context,
- foreign_exception: bool,
-) -> Result<EHAction, ()> {
+unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction, ()> {
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
let mut ip_before_instr: c_int = 0;
let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr);
get_text_start: &|| uw::_Unwind_GetTextRelBase(context),
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
};
- eh::find_eh_action(lsda, &eh_context, foreign_exception)
+ eh::find_eh_action(lsda, &eh_context)
}
// Frame unwind info registration
// - os=none ("bare metal" targets)
// - os=uefi
// - nvptx64-nvidia-cuda
- // - avr-unknown-unknown
+ // - arch=avr
#[path = "dummy.rs"]
mod real_imp;
}
/// Handler in libstd called when a panic object is dropped outside of
/// `catch_unwind`.
fn __rust_drop_panic() -> !;
+
+ /// Handler in libstd called when a foreign exception is caught.
+ fn __rust_foreign_exception() -> !;
}
mod dwarf;
extern "system" {
#[unwind(allowed)]
- pub fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
+ fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
}
_CxxThrowException(throw_ptr, &mut THROW_INFO as *mut _ as *mut _);
}
pub unsafe fn cleanup(payload: *mut u8) -> Box<dyn Any + Send> {
- let exception = &mut *(payload as *mut Exception);
- exception.data.take().unwrap()
+ // A NULL payload here means that we got here from the catch (...) of
+ // __rust_try. This happens when a non-Rust foreign exception is caught.
+ if payload.is_null() {
+ super::__rust_foreign_exception();
+ } else {
+ let exception = &mut *(payload as *mut Exception);
+ exception.data.take().unwrap()
+ }
}
// This is required by the compiler to exist (e.g., it's a lang item), but
"InstrProfilingMergeFile.c",
"InstrProfilingNameVar.c",
"InstrProfilingPlatformDarwin.c",
+ "InstrProfilingPlatformFuchsia.c",
"InstrProfilingPlatformLinux.c",
"InstrProfilingPlatformOther.c",
"InstrProfilingPlatformWindows.c",
// This should be a pretty good heuristic for when to set
// COMPILER_RT_HAS_ATOMICS
if env::var_os("CARGO_CFG_TARGET_HAS_ATOMIC")
- .map(|features| features.to_string_lossy().to_lowercase().contains("cas"))
+ .map(|features| features.to_string_lossy().to_lowercase().contains("ptr"))
.unwrap_or(false)
{
cfg.define("COMPILER_RT_HAS_ATOMICS", Some("1"));
panic_abort = { path = "../panic_abort" }
core = { path = "../core" }
libc = { version = "0.2.74", default-features = false, features = ['rustc-dep-of-std'] }
-compiler_builtins = { version = "0.1.32" }
+compiler_builtins = { version = "0.1.35" }
profiler_builtins = { path = "../profiler_builtins", optional = true }
unwind = { path = "../unwind" }
hashbrown = { version = "0.8.1", default-features = false, features = ['rustc-dep-of-std'] }
// - os=none ("bare metal" targets)
// - mipsel-sony-psp
// - nvptx64-nvidia-cuda
- // - avr-unknown-unknown
+ // - arch=avr
// - tvos (aarch64-apple-tvos, x86_64-apple-tvos)
// - uefi (x86_64-unknown-uefi, i686-unknown-uefi)
// - JSON targets
unsafe fn grow_impl(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
zeroed: bool,
) -> Result<NonNull<[u8]>, AllocErr> {
debug_assert!(
- new_size >= layout.size(),
- "`new_size` must be greater than or equal to `layout.size()`"
+ new_layout.size() >= old_layout.size(),
+ "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
- match layout.size() {
- // SAFETY: the caller must ensure that the `new_size` does not overflow.
- // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid for a Layout.
- 0 => unsafe {
- let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
- self.alloc_impl(new_layout, zeroed)
- },
+ match old_layout.size() {
+ 0 => self.alloc_impl(new_layout, zeroed),
// SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
// as required by safety conditions. Other conditions must be upheld by the caller
- old_size => unsafe {
- // `realloc` probably checks for `new_size >= size` or something similar.
- intrinsics::assume(new_size >= layout.size());
+ old_size if old_layout.align() == new_layout.align() => unsafe {
+ let new_size = new_layout.size();
+
+ // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
+ intrinsics::assume(new_size >= old_layout.size());
- let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), layout, new_size);
+ let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
if zeroed {
raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
}
Ok(NonNull::slice_from_raw_parts(ptr, new_size))
},
+
+ // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
+ // both the old and new memory allocation are valid for reads and writes for `old_size`
+ // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
+ // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
+ // for `dealloc` must be upheld by the caller.
+ old_size => unsafe {
+ let new_ptr = self.alloc_impl(new_layout, zeroed)?;
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), old_size);
+ self.dealloc(ptr, old_layout);
+ Ok(new_ptr)
+ },
}
}
}
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: all conditions must be upheld by the caller
- unsafe { self.grow_impl(ptr, layout, new_size, false) }
+ unsafe { self.grow_impl(ptr, old_layout, new_layout, false) }
}
#[inline]
unsafe fn grow_zeroed(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
// SAFETY: all conditions must be upheld by the caller
- unsafe { self.grow_impl(ptr, layout, new_size, true) }
+ unsafe { self.grow_impl(ptr, old_layout, new_layout, true) }
}
#[inline]
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
- layout: Layout,
- new_size: usize,
+ old_layout: Layout,
+ new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocErr> {
debug_assert!(
- new_size <= layout.size(),
- "`new_size` must be smaller than or equal to `layout.size()`"
+ new_layout.size() <= old_layout.size(),
+ "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
);
- match new_size {
+ match new_layout.size() {
// SAFETY: conditions must be upheld by the caller
0 => unsafe {
- self.dealloc(ptr, layout);
- Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0))
+ self.dealloc(ptr, old_layout);
+ Ok(NonNull::slice_from_raw_parts(new_layout.dangling(), 0))
},
// SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
- new_size => unsafe {
- // `realloc` probably checks for `new_size <= size` or something similar.
- intrinsics::assume(new_size <= layout.size());
+ new_size if old_layout.align() == new_layout.align() => unsafe {
+ // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
+ intrinsics::assume(new_size <= old_layout.size());
- let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), layout, new_size);
+ let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
Ok(NonNull::slice_from_raw_parts(ptr, new_size))
},
+
+ // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
+ // both the old and new memory allocation are valid for reads and writes for `new_size`
+ // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
+ // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
+ // for `dealloc` must be upheld by the caller.
+ new_size => unsafe {
+ let new_ptr = self.alloc(new_layout)?;
+ ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_mut_ptr(), new_size);
+ self.dealloc(ptr, old_layout);
+ Ok(new_ptr)
+ },
}
}
}
/// themselves through the [`Display`] and [`Debug`] traits, and may provide
/// cause chain information:
///
-/// The [`source`] method is generally used when errors cross "abstraction
-/// boundaries". If one module must report an error that is caused by an error
-/// from a lower-level module, it can allow access to that error via the
-/// [`source`] method. This makes it possible for the high-level module to
-/// provide its own errors while also revealing some of the implementation for
-/// debugging via [`source`] chains.
+/// [`Error::source()`] is generally used when errors cross
+/// "abstraction boundaries". If one module must report an error that is caused
+/// by an error from a lower-level module, it can allow accessing that error
+/// via [`Error::source()`]. This makes it possible for the high-level
+/// module to provide its own errors while also revealing some of the
+/// implementation for debugging via `source` chains.
///
/// [`Result<T, E>`]: Result
-/// [`source`]: Error::source
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Error: Debug + Display {
/// The lower-level source of this error, if any.
}
/// Returns an iterator starting with the current error and continuing with
- /// recursively calling [`source`].
+ /// recursively calling [`Error::source`].
///
/// If you want to omit the current error and only use its sources,
/// use `skip(1)`.
/// assert!(iter.next().is_none());
/// assert!(iter.next().is_none());
/// ```
- ///
- /// [`source`]: Error::source
#[unstable(feature = "error_iter", issue = "58520")]
#[inline]
pub fn chain(&self) -> Chain<'_> {
BufWriter { inner: Some(inner), buf: Vec::with_capacity(capacity), panicked: false }
}
+ /// Send data in our local buffer into the inner writer, looping as
+ /// necessary until either it's all been sent or an error occurs.
+ ///
+ /// Because all the data in the buffer has been reported to our owner as
+ /// "successfully written" (by returning nonzero success values from
+ /// `write`), any 0-length writes from `inner` must be reported as i/o
+ /// errors from this method.
fn flush_buf(&mut self) -> io::Result<()> {
- let mut written = 0;
- let len = self.buf.len();
- let mut ret = Ok(());
- while written < len {
+ /// Helper struct to ensure the buffer is updated after all the writes
+ /// are complete. It tracks the number of written bytes and drains them
+ /// all from the front of the buffer when dropped.
+ struct BufGuard<'a> {
+ buffer: &'a mut Vec<u8>,
+ written: usize,
+ }
+
+ impl<'a> BufGuard<'a> {
+ fn new(buffer: &'a mut Vec<u8>) -> Self {
+ Self { buffer, written: 0 }
+ }
+
+ /// The unwritten part of the buffer
+ fn remaining(&self) -> &[u8] {
+ &self.buffer[self.written..]
+ }
+
+ /// Flag some bytes as removed from the front of the buffer
+ fn consume(&mut self, amt: usize) {
+ self.written += amt;
+ }
+
+ /// true if all of the bytes have been written
+ fn done(&self) -> bool {
+ self.written >= self.buffer.len()
+ }
+ }
+
+ impl Drop for BufGuard<'_> {
+ fn drop(&mut self) {
+ if self.written > 0 {
+ self.buffer.drain(..self.written);
+ }
+ }
+ }
+
+ let mut guard = BufGuard::new(&mut self.buf);
+ let inner = self.inner.as_mut().unwrap();
+ while !guard.done() {
self.panicked = true;
- let r = self.inner.as_mut().unwrap().write(&self.buf[written..]);
+ let r = inner.write(guard.remaining());
self.panicked = false;
match r {
Ok(0) => {
- ret =
- Err(Error::new(ErrorKind::WriteZero, "failed to write the buffered data"));
- break;
+ return Err(Error::new(
+ ErrorKind::WriteZero,
+ "failed to write the buffered data",
+ ));
}
- Ok(n) => written += n,
+ Ok(n) => guard.consume(n),
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
- Err(e) => {
- ret = Err(e);
- break;
- }
+ Err(e) => return Err(e),
}
}
- if written > 0 {
- self.buf.drain(..written);
- }
- ret
+ Ok(())
+ }
+
+ /// Buffer some data without flushing it, regardless of the size of the
+ /// data. Writes as much as possible without exceeding capacity. Returns
+ /// the number of bytes written.
+ fn write_to_buf(&mut self, buf: &[u8]) -> usize {
+ let available = self.buf.capacity() - self.buf.len();
+ let amt_to_buffer = available.min(buf.len());
+ self.buf.extend_from_slice(&buf[..amt_to_buffer]);
+ amt_to_buffer
}
/// Gets a reference to the underlying writer.
if self.buf.len() + buf.len() > self.buf.capacity() {
self.flush_buf()?;
}
+ // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919
if buf.len() >= self.buf.capacity() {
self.panicked = true;
let r = self.get_mut().write(buf);
self.panicked = false;
r
} else {
- self.buf.write(buf)
+ self.buf.extend_from_slice(buf);
+ Ok(buf.len())
+ }
+ }
+
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ // Normally, `write_all` just calls `write` in a loop. We can do better
+ // by calling `self.get_mut().write_all()` directly, which avoids
+ // round trips through the buffer in the event of a series of partial
+ // writes in some circumstances.
+ if self.buf.len() + buf.len() > self.buf.capacity() {
+ self.flush_buf()?;
+ }
+ // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919
+ if buf.len() >= self.buf.capacity() {
+ self.panicked = true;
+ let r = self.get_mut().write_all(buf);
+ self.panicked = false;
+ r
+ } else {
+ self.buf.extend_from_slice(buf);
+ Ok(())
}
}
if self.buf.len() + total_len > self.buf.capacity() {
self.flush_buf()?;
}
+ // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919
if total_len >= self.buf.capacity() {
self.panicked = true;
let r = self.get_mut().write_vectored(bufs);
self.panicked = false;
r
} else {
- self.buf.write_vectored(bufs)
+ bufs.iter().for_each(|b| self.buf.extend_from_slice(b));
+ Ok(total_len)
}
}
///
/// Seeking always writes out the internal buffer before seeking.
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
- self.flush_buf().and_then(|_| self.get_mut().seek(pos))
+ self.flush_buf()?;
+ self.get_mut().seek(pos)
}
}
}
}
+/// Private helper struct for implementing the line-buffered writing logic.
+/// This shim temporarily wraps a BufWriter, and uses its internals to
+/// implement a line-buffered writer (specifically by using the internal
+/// methods like write_to_buf and flush_buf). In this way, a more
+/// efficient abstraction can be created than one that only had access to
+/// `write` and `flush`, without needlessly duplicating a lot of the
+/// implementation details of BufWriter. This also allows existing
+/// `BufWriters` to be temporarily given line-buffering logic; this is what
+/// enables Stdout to be alternately in line-buffered or block-buffered mode.
+#[derive(Debug)]
+pub(super) struct LineWriterShim<'a, W: Write> {
+ buffer: &'a mut BufWriter<W>,
+}
+
+impl<'a, W: Write> LineWriterShim<'a, W> {
+ pub fn new(buffer: &'a mut BufWriter<W>) -> Self {
+ Self { buffer }
+ }
+
+ /// Get a mutable reference to the inner writer (that is, the writer
+ /// wrapped by the BufWriter). Be careful with this writer, as writes to
+ /// it will bypass the buffer.
+ fn inner_mut(&mut self) -> &mut W {
+ self.buffer.get_mut()
+ }
+
+ /// Get the content currently buffered in self.buffer
+ fn buffered(&self) -> &[u8] {
+ self.buffer.buffer()
+ }
+
+ /// Flush the buffer iff the last byte is a newline (indicating that an
+ /// earlier write only succeeded partially, and we want to retry flushing
+ /// the buffered line before continuing with a subsequent write)
+ fn flush_if_completed_line(&mut self) -> io::Result<()> {
+ match self.buffered().last().copied() {
+ Some(b'\n') => self.buffer.flush_buf(),
+ _ => Ok(()),
+ }
+ }
+}
+
+impl<'a, W: Write> Write for LineWriterShim<'a, W> {
+ /// Write some data into this BufReader with line buffering. This means
+ /// that, if any newlines are present in the data, the data up to the last
+ /// newline is sent directly to the underlying writer, and data after it
+ /// is buffered. Returns the number of bytes written.
+ ///
+ /// This function operates on a "best effort basis"; in keeping with the
+ /// convention of `Write::write`, it makes at most one attempt to write
+ /// new data to the underlying writer. If that write only reports a partial
+ /// success, the remaining data will be buffered.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it ends with a
+ /// newline, even if the incoming data does not contain any newlines.
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let newline_idx = match memchr::memrchr(b'\n', buf) {
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write (which may flush if
+ // we exceed the inner buffer's size)
+ None => {
+ self.flush_if_completed_line()?;
+ return self.buffer.write(buf);
+ }
+ // Otherwise, arrange for the lines to be written directly to the
+ // inner writer.
+ Some(newline_idx) => newline_idx + 1,
+ };
+
+ // Flush existing content to prepare for our write. We have to do this
+ // before attempting to write `buf` in order to maintain consistency;
+ // if we add `buf` to the buffer then try to flush it all at once,
+ // we're obligated to return Ok(), which would mean suppressing any
+ // errors that occur during flush.
+ self.buffer.flush_buf()?;
+
+ // This is what we're going to try to write directly to the inner
+ // writer. The rest will be buffered, if nothing goes wrong.
+ let lines = &buf[..newline_idx];
+
+ // Write `lines` directly to the inner writer. In keeping with the
+ // `write` convention, make at most one attempt to add new (unbuffered)
+ // data. Because this write doesn't touch the BufWriter state directly,
+ // and the buffer is known to be empty, we don't need to worry about
+ // self.buffer.panicked here.
+ let flushed = self.inner_mut().write(lines)?;
+
+ // If buffer returns Ok(0), propagate that to the caller without
+ // doing additional buffering; otherwise we're just guaranteeing
+ // an "ErrorKind::WriteZero" later.
+ if flushed == 0 {
+ return Ok(0);
+ }
+
+ // Now that the write has succeeded, buffer the rest (or as much of
+ // the rest as possible). If there were any unwritten newlines, we
+ // only buffer out to the last unwritten newline that fits in the
+ // buffer; this helps prevent flushing partial lines on subsequent
+ // calls to LineWriterShim::write.
+
+ // Handle the cases in order of most-common to least-common, under
+ // the presumption that most writes succeed in totality, and that most
+ // writes are smaller than the buffer.
+ // - Is this a partial line (ie, no newlines left in the unwritten tail)
+ // - If not, does the data out to the last unwritten newline fit in
+ // the buffer?
+ // - If not, scan for the last newline that *does* fit in the buffer
+ let tail = if flushed >= newline_idx {
+ &buf[flushed..]
+ } else if newline_idx - flushed <= self.buffer.capacity() {
+ &buf[flushed..newline_idx]
+ } else {
+ let scan_area = &buf[flushed..];
+ let scan_area = &scan_area[..self.buffer.capacity()];
+ match memchr::memrchr(b'\n', scan_area) {
+ Some(newline_idx) => &scan_area[..newline_idx + 1],
+ None => scan_area,
+ }
+ };
+
+ let buffered = self.buffer.write_to_buf(tail);
+ Ok(flushed + buffered)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+
+ /// Write some vectored data into this BufReader with line buffering. This
+ /// means that, if any newlines are present in the data, the data up to
+ /// and including the buffer containing the last newline is sent directly
+ /// to the inner writer, and the data after it is buffered. Returns the
+ /// number of bytes written.
+ ///
+ /// This function operates on a "best effort basis"; in keeping with the
+ /// convention of `Write::write`, it makes at most one attempt to write
+ /// new data to the underlying writer.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it contains any
+ /// newlines.
+ ///
+ /// Because sorting through an array of `IoSlice` can be a bit convoluted,
+ /// This method differs from write in the following ways:
+ ///
+ /// - It attempts to write the full content of all the buffers up to and
+ /// including the one containing the last newline. This means that it
+ /// may attempt to write a partial line, that buffer has data past the
+ /// newline.
+ /// - If the write only reports partial success, it does not attempt to
+ /// find the precise location of the written bytes and buffer the rest.
+ ///
+ /// If the underlying vector doesn't support vectored writing, we instead
+ /// simply write the first non-empty buffer with `write`. This way, we
+ /// get the benefits of more granular partial-line handling without losing
+ /// anything in efficiency
+ fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+ // If there's no specialized behavior for write_vectored, just use
+ // write. This has the benefit of more granular partial-line handling.
+ if !self.is_write_vectored() {
+ return match bufs.iter().find(|buf| !buf.is_empty()) {
+ Some(buf) => self.write(buf),
+ None => Ok(0),
+ };
+ }
+
+ // Find the buffer containing the last newline
+ let last_newline_buf_idx = bufs
+ .iter()
+ .enumerate()
+ .rev()
+ .find_map(|(i, buf)| memchr::memchr(b'\n', buf).map(|_| i));
+
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write
+ let last_newline_buf_idx = match last_newline_buf_idx {
+ // No newlines; just do a normal buffered write
+ None => {
+ self.flush_if_completed_line()?;
+ return self.buffer.write_vectored(bufs);
+ }
+ Some(i) => i,
+ };
+
+ // Flush existing content to prepare for our write
+ self.buffer.flush_buf()?;
+
+ // This is what we're going to try to write directly to the inner
+ // writer. The rest will be buffered, if nothing goes wrong.
+ let (lines, tail) = bufs.split_at(last_newline_buf_idx + 1);
+
+ // Write `lines` directly to the inner writer. In keeping with the
+ // `write` convention, make at most one attempt to add new (unbuffered)
+ // data. Because this write doesn't touch the BufWriter state directly,
+ // and the buffer is known to be empty, we don't need to worry about
+ // self.panicked here.
+ let flushed = self.inner_mut().write_vectored(lines)?;
+
+ // If inner returns Ok(0), propagate that to the caller without
+ // doing additional buffering; otherwise we're just guaranteeing
+ // an "ErrorKind::WriteZero" later.
+ if flushed == 0 {
+ return Ok(0);
+ }
+
+ // Don't try to reconstruct the exact amount written; just bail
+ // in the event of a partial write
+ let lines_len = lines.iter().map(|buf| buf.len()).sum();
+ if flushed < lines_len {
+ return Ok(flushed);
+ }
+
+ // Now that the write has succeeded, buffer the rest (or as much of the
+ // rest as possible)
+ let buffered: usize = tail
+ .iter()
+ .filter(|buf| !buf.is_empty())
+ .map(|buf| self.buffer.write_to_buf(buf))
+ .take_while(|&n| n > 0)
+ .sum();
+
+ Ok(flushed + buffered)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.buffer.is_write_vectored()
+ }
+
+ /// Write some data into this BufReader with line buffering. This means
+ /// that, if any newlines are present in the data, the data up to the last
+ /// newline is sent directly to the underlying writer, and data after it
+ /// is buffered.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it contains any
+ /// newlines, even if the incoming data does not contain any newlines.
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ match memchr::memrchr(b'\n', buf) {
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write (which may flush if
+ // we exceed the inner buffer's size)
+ None => {
+ self.flush_if_completed_line()?;
+ self.buffer.write_all(buf)
+ }
+ Some(newline_idx) => {
+ let (lines, tail) = buf.split_at(newline_idx + 1);
+
+ if self.buffered().is_empty() {
+ self.inner_mut().write_all(lines)?;
+ } else {
+ // If there is any buffered data, we add the incoming lines
+ // to that buffer before flushing, which saves us at least
+ // one write call. We can't really do this with `write`,
+ // since we can't do this *and* not suppress errors *and*
+ // report a consistent state to the caller in a return
+ // value, but here in write_all it's fine.
+ self.buffer.write_all(lines)?;
+ self.buffer.flush_buf()?;
+ }
+
+ self.buffer.write_all(tail)
+ }
+ }
+ }
+}
+
/// Wraps a writer and buffers output to it, flushing whenever a newline
/// (`0x0a`, `'\n'`) is detected.
///
#[stable(feature = "rust1", since = "1.0.0")]
pub struct LineWriter<W: Write> {
inner: BufWriter<W>,
- need_flush: bool,
}
impl<W: Write> LineWriter<W> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn with_capacity(capacity: usize, inner: W) -> LineWriter<W> {
- LineWriter { inner: BufWriter::with_capacity(capacity, inner), need_flush: false }
+ LineWriter { inner: BufWriter::with_capacity(capacity, inner) }
}
/// Gets a reference to the underlying writer.
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn into_inner(self) -> Result<W, IntoInnerError<LineWriter<W>>> {
- self.inner.into_inner().map_err(|IntoInnerError(buf, e)| {
- IntoInnerError(LineWriter { inner: buf, need_flush: false }, e)
- })
+ self.inner
+ .into_inner()
+ .map_err(|IntoInnerError(buf, e)| IntoInnerError(LineWriter { inner: buf }, e))
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<W: Write> Write for LineWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- if self.need_flush {
- self.flush()?;
- }
-
- // Find the last newline character in the buffer provided. If found then
- // we're going to write all the data up to that point and then flush,
- // otherwise we just write the whole block to the underlying writer.
- let i = match memchr::memrchr(b'\n', buf) {
- Some(i) => i,
- None => return self.inner.write(buf),
- };
-
- // Ok, we're going to write a partial amount of the data given first
- // followed by flushing the newline. After we've successfully written
- // some data then we *must* report that we wrote that data, so future
- // errors are ignored. We set our internal `need_flush` flag, though, in
- // case flushing fails and we need to try it first next time.
- let n = self.inner.write(&buf[..=i])?;
- self.need_flush = true;
- if self.flush().is_err() || n != i + 1 {
- return Ok(n);
- }
+ LineWriterShim::new(&mut self.inner).write(buf)
+ }
- // At this point we successfully wrote `i + 1` bytes and flushed it out,
- // meaning that the entire line is now flushed out on the screen. While
- // we can attempt to finish writing the rest of the data provided.
- // Remember though that we ignore errors here as we've successfully
- // written data, so we need to report that.
- match self.inner.write(&buf[i + 1..]) {
- Ok(i) => Ok(n + i),
- Err(_) => Ok(n),
- }
+ fn flush(&mut self) -> io::Result<()> {
+ self.inner.flush()
}
- // Vectored writes are very similar to the writes above, but adjusted for
- // the list of buffers that we have to write.
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
- if self.need_flush {
- self.flush()?;
- }
+ LineWriterShim::new(&mut self.inner).write_vectored(bufs)
+ }
- // Find the last newline, and failing that write the whole buffer
- let last_newline = bufs.iter().enumerate().rev().find_map(|(i, buf)| {
- let pos = memchr::memrchr(b'\n', buf)?;
- Some((i, pos))
- });
- let (i, j) = match last_newline {
- Some(pair) => pair,
- None => return self.inner.write_vectored(bufs),
- };
- let (prefix, suffix) = bufs.split_at(i);
- let (buf, suffix) = suffix.split_at(1);
- let buf = &buf[0];
-
- // Write everything up to the last newline, flushing afterwards. Note
- // that only if we finished our entire `write_vectored` do we try the
- // subsequent
- // `write`
- let mut n = 0;
- let prefix_amt = prefix.iter().map(|i| i.len()).sum();
- if prefix_amt > 0 {
- n += self.inner.write_vectored(prefix)?;
- self.need_flush = true;
- }
- if n == prefix_amt {
- match self.inner.write(&buf[..=j]) {
- Ok(m) => n += m,
- Err(e) if n == 0 => return Err(e),
- Err(_) => return Ok(n),
- }
- self.need_flush = true;
- }
- if self.flush().is_err() || n != j + 1 + prefix_amt {
- return Ok(n);
- }
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
- // ... and now write out everything remaining
- match self.inner.write(&buf[j + 1..]) {
- Ok(i) => n += i,
- Err(_) => return Ok(n),
- }
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ LineWriterShim::new(&mut self.inner).write_all(buf)
+ }
- if suffix.iter().map(|s| s.len()).sum::<usize>() == 0 {
- return Ok(n);
- }
- match self.inner.write_vectored(suffix) {
- Ok(i) => Ok(n + i),
- Err(_) => Ok(n),
- }
+ fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
+ LineWriterShim::new(&mut self.inner).write_all_vectored(bufs)
}
- fn flush(&mut self) -> io::Result<()> {
- self.inner.flush()?;
- self.need_flush = false;
- Ok(())
+ fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
+ LineWriterShim::new(&mut self.inner).write_fmt(fmt)
}
}
#[cfg(test)]
mod tests {
use crate::io::prelude::*;
- use crate::io::{self, BufReader, BufWriter, IoSlice, LineWriter, SeekFrom};
+ use crate::io::{self, BufReader, BufWriter, ErrorKind, IoSlice, LineWriter, SeekFrom};
use crate::sync::atomic::{AtomicUsize, Ordering};
use crate::thread;
lengths: Vec<usize>,
}
+ // FIXME: rustfmt and tidy disagree about the correct formatting of this
+ // function. This leads to issues for users with editors configured to
+ // rustfmt-on-save.
impl Read for ShortReader {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) }
assert_eq!(v, []);
}
- #[test]
- fn test_line_buffer_fail_flush() {
- // Issue #32085
- struct FailFlushWriter<'a>(&'a mut Vec<u8>);
-
- impl Write for FailFlushWriter<'_> {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- self.0.extend_from_slice(buf);
- Ok(buf.len())
- }
- fn flush(&mut self) -> io::Result<()> {
- Err(io::Error::new(io::ErrorKind::Other, "flush failed"))
- }
- }
-
- let mut buf = Vec::new();
- {
- let mut writer = LineWriter::new(FailFlushWriter(&mut buf));
- let to_write = b"abc\ndef";
- if let Ok(written) = writer.write(to_write) {
- assert!(written < to_write.len(), "didn't flush on new line");
- // PASS
- return;
- }
- }
- assert!(buf.is_empty(), "write returned an error but wrote data");
- }
-
#[test]
fn test_line_buffer() {
let mut writer = LineWriter::new(Vec::new());
b.iter(|| BufWriter::new(io::sink()));
}
- struct AcceptOneThenFail {
- written: bool,
- flushed: bool,
+ /// A simple `Write` target, designed to be wrapped by `LineWriter` /
+ /// `BufWriter` / etc, that can have its `write` & `flush` behavior
+ /// configured
+ #[derive(Default, Clone)]
+ struct ProgrammableSink {
+ // Writes append to this slice
+ pub buffer: Vec<u8>,
+
+ // Flush sets this flag
+ pub flushed: bool,
+
+ // If true, writes will always be an error
+ pub always_write_error: bool,
+
+ // If true, flushes will always be an error
+ pub always_flush_error: bool,
+
+ // If set, only up to this number of bytes will be written in a single
+ // call to `write`
+ pub accept_prefix: Option<usize>,
+
+ // If set, counts down with each write, and writes return an error
+ // when it hits 0
+ pub max_writes: Option<usize>,
+
+ // If set, attempting to write when max_writes == Some(0) will be an
+ // error; otherwise, it will return Ok(0).
+ pub error_after_max_writes: bool,
}
- impl Write for AcceptOneThenFail {
+ impl Write for ProgrammableSink {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
- if !self.written {
- assert_eq!(data, b"a\nb\n");
- self.written = true;
- Ok(data.len())
- } else {
- Err(io::Error::new(io::ErrorKind::NotFound, "test"))
+ if self.always_write_error {
+ return Err(io::Error::new(io::ErrorKind::Other, "test - always_write_error"));
}
+
+ match self.max_writes {
+ Some(0) if self.error_after_max_writes => {
+ return Err(io::Error::new(io::ErrorKind::Other, "test - max_writes"));
+ }
+ Some(0) => return Ok(0),
+ Some(ref mut count) => *count -= 1,
+ None => {}
+ }
+
+ let len = match self.accept_prefix {
+ None => data.len(),
+ Some(prefix) => data.len().min(prefix),
+ };
+
+ let data = &data[..len];
+ self.buffer.extend_from_slice(data);
+
+ Ok(len)
}
fn flush(&mut self) -> io::Result<()> {
- assert!(self.written);
- assert!(!self.flushed);
- self.flushed = true;
- Err(io::Error::new(io::ErrorKind::Other, "test"))
+ if self.always_flush_error {
+ Err(io::Error::new(io::ErrorKind::Other, "test - always_flush_error"))
+ } else {
+ self.flushed = true;
+ Ok(())
+ }
}
}
+ /// Previously the `LineWriter` could successfully write some bytes but
+ /// then fail to report that it has done so. Additionally, an erroneous
+ /// flush after a successful write was permanently ignored.
+ ///
+ /// Test that a line writer correctly reports the number of written bytes,
+ /// and that it attempts to flush buffered lines from previous writes
+ /// before processing new data
+ ///
+ /// Regression test for #37807
#[test]
fn erroneous_flush_retried() {
- let a = AcceptOneThenFail { written: false, flushed: false };
+ let writer = ProgrammableSink {
+ // Only write up to 4 bytes at a time
+ accept_prefix: Some(4),
- let mut l = LineWriter::new(a);
- assert_eq!(l.write(b"a\nb\na").unwrap(), 4);
- assert!(l.get_ref().written);
- assert!(l.get_ref().flushed);
- l.get_mut().flushed = false;
+ // Accept the first two writes, then error the others
+ max_writes: Some(2),
+ error_after_max_writes: true,
+
+ ..Default::default()
+ };
- assert_eq!(l.write(b"a").unwrap_err().kind(), io::ErrorKind::Other)
+ // This should write the first 4 bytes. The rest will be buffered, out
+ // to the last newline.
+ let mut writer = LineWriter::new(writer);
+ assert_eq!(writer.write(b"a\nb\nc\nd\ne").unwrap(), 8);
+
+ // This write should attempt to flush "c\nd\n", then buffer "e". No
+ // errors should happen here because no further writes should be
+ // attempted against `writer`.
+ assert_eq!(writer.write(b"e").unwrap(), 1);
+ assert_eq!(&writer.get_ref().buffer, b"a\nb\nc\nd\n");
}
#[test]
0,
);
assert_eq!(a.write_vectored(&[IoSlice::new(b"a\nb"),]).unwrap(), 3);
- assert_eq!(a.get_ref(), b"\nabaca\n");
+ assert_eq!(a.get_ref(), b"\nabaca\nb");
}
#[test]
fn line_vectored_partial_and_errors() {
+ use crate::collections::VecDeque;
+
enum Call {
Write { inputs: Vec<&'static [u8]>, output: io::Result<usize> },
Flush { output: io::Result<()> },
}
+
+ #[derive(Default)]
struct Writer {
- calls: Vec<Call>,
+ calls: VecDeque<Call>,
}
impl Write for Writer {
}
fn write_vectored(&mut self, buf: &[IoSlice<'_>]) -> io::Result<usize> {
- match self.calls.pop().unwrap() {
+ match self.calls.pop_front().expect("unexpected call to write") {
Call::Write { inputs, output } => {
assert_eq!(inputs, buf.iter().map(|b| &**b).collect::<Vec<_>>());
output
}
- _ => panic!("unexpected call to write"),
+ Call::Flush { .. } => panic!("unexpected call to write; expected a flush"),
}
}
+ fn is_write_vectored(&self) -> bool {
+ true
+ }
+
fn flush(&mut self) -> io::Result<()> {
- match self.calls.pop().unwrap() {
+ match self.calls.pop_front().expect("Unexpected call to flush") {
Call::Flush { output } => output,
- _ => panic!("unexpected call to flush"),
+ Call::Write { .. } => panic!("unexpected call to flush; expected a write"),
}
}
}
}
// partial writes keep going
- let mut a = LineWriter::new(Writer { calls: Vec::new() });
+ let mut a = LineWriter::new(Writer::default());
a.write_vectored(&[IoSlice::new(&[]), IoSlice::new(b"abc")]).unwrap();
- a.get_mut().calls.push(Call::Flush { output: Ok(()) });
- a.get_mut().calls.push(Call::Write { inputs: vec![b"bcx\n"], output: Ok(4) });
- a.get_mut().calls.push(Call::Write { inputs: vec![b"abcx\n"], output: Ok(1) });
+
+ a.get_mut().calls.push_back(Call::Write { inputs: vec![b"abc"], output: Ok(1) });
+ a.get_mut().calls.push_back(Call::Write { inputs: vec![b"bc"], output: Ok(2) });
+ a.get_mut().calls.push_back(Call::Write { inputs: vec![b"x", b"\n"], output: Ok(2) });
+
a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\n")]).unwrap();
- a.get_mut().calls.push(Call::Flush { output: Ok(()) });
+
+ a.get_mut().calls.push_back(Call::Flush { output: Ok(()) });
a.flush().unwrap();
// erroneous writes stop and don't write more
- a.get_mut().calls.push(Call::Write { inputs: vec![b"x\n"], output: Err(err()) });
- assert_eq!(a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\na")]).unwrap(), 2);
- a.get_mut().calls.push(Call::Flush { output: Ok(()) });
- a.get_mut().calls.push(Call::Write { inputs: vec![b"x\n"], output: Ok(2) });
+ a.get_mut().calls.push_back(Call::Write { inputs: vec![b"x", b"\na"], output: Err(err()) });
+ a.get_mut().calls.push_back(Call::Flush { output: Ok(()) });
+ assert!(a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\na")]).is_err());
a.flush().unwrap();
fn err() -> io::Error {
io::Error::new(io::ErrorKind::Other, "x")
}
}
+
+ /// Test that, in cases where vectored writing is not enabled, the
+ /// LineWriter uses the normal `write` call, which more-correctly handles
+ /// partial lines
+ #[test]
+ fn line_vectored_ignored() {
+ let writer = ProgrammableSink::default();
+ let mut writer = LineWriter::new(writer);
+
+ let content = [
+ IoSlice::new(&[]),
+ IoSlice::new(b"Line 1\nLine"),
+ IoSlice::new(b" 2\nLine 3\nL"),
+ IoSlice::new(&[]),
+ IoSlice::new(&[]),
+ IoSlice::new(b"ine 4"),
+ IoSlice::new(b"\nLine 5\n"),
+ ];
+
+ let count = writer.write_vectored(&content).unwrap();
+ assert_eq!(count, 11);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n");
+
+ let count = writer.write_vectored(&content[2..]).unwrap();
+ assert_eq!(count, 11);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n");
+
+ let count = writer.write_vectored(&content[5..]).unwrap();
+ assert_eq!(count, 5);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n");
+
+ let count = writer.write_vectored(&content[6..]).unwrap();
+ assert_eq!(count, 8);
+ assert_eq!(
+ writer.get_ref().buffer.as_slice(),
+ b"Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n".as_ref()
+ );
+ }
+
+ /// Test that, given this input:
+ ///
+ /// Line 1\n
+ /// Line 2\n
+ /// Line 3\n
+ /// Line 4
+ ///
+ /// And given a result that only writes to midway through Line 2
+ ///
+ /// That only up to the end of Line 3 is buffered
+ ///
+ /// This behavior is desirable because it prevents flushing partial lines
+ #[test]
+ fn partial_write_buffers_line() {
+ let writer = ProgrammableSink { accept_prefix: Some(13), ..Default::default() };
+ let mut writer = LineWriter::new(writer);
+
+ assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3\nLine4").unwrap(), 21);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2");
+
+ assert_eq!(writer.write(b"Line 4").unwrap(), 6);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n");
+ }
+
+ /// Test that, given this input:
+ ///
+ /// Line 1\n
+ /// Line 2\n
+ /// Line 3
+ ///
+ /// And given that the full write of lines 1 and 2 was successful
+ /// That data up to Line 3 is buffered
+ #[test]
+ fn partial_line_buffered_after_line_write() {
+ let writer = ProgrammableSink::default();
+ let mut writer = LineWriter::new(writer);
+
+ assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3").unwrap(), 20);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\n");
+
+ assert!(writer.flush().is_ok());
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3");
+ }
+
+ /// Test that, given a partial line that exceeds the length of
+ /// LineBuffer's buffer (that is, without a trailing newline), that that
+ /// line is written to the inner writer
+ #[test]
+ fn long_line_flushed() {
+ let writer = ProgrammableSink::default();
+ let mut writer = LineWriter::with_capacity(5, writer);
+
+ assert_eq!(writer.write(b"0123456789").unwrap(), 10);
+ assert_eq!(&writer.get_ref().buffer, b"0123456789");
+ }
+
+ /// Test that, given a very long partial line *after* successfully
+ /// flushing a complete line, that that line is buffered unconditionally,
+ /// and no additional writes take place. This assures the property that
+ /// `write` should make at-most-one attempt to write new data.
+ #[test]
+ fn line_long_tail_not_flushed() {
+ let writer = ProgrammableSink::default();
+ let mut writer = LineWriter::with_capacity(5, writer);
+
+ // Assert that Line 1\n is flushed, and 01234 is buffered
+ assert_eq!(writer.write(b"Line 1\n0123456789").unwrap(), 12);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n");
+
+ // Because the buffer is full, this subsequent write will flush it
+ assert_eq!(writer.write(b"5").unwrap(), 1);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n01234");
+ }
+
+ /// Test that, if an attempt to pre-flush buffered data returns Ok(0),
+ /// this is propagated as an error.
+ #[test]
+ fn line_buffer_write0_error() {
+ let writer = ProgrammableSink {
+ // Accept one write, then return Ok(0) on subsequent ones
+ max_writes: Some(1),
+
+ ..Default::default()
+ };
+ let mut writer = LineWriter::new(writer);
+
+ // This should write "Line 1\n" and buffer "Partial"
+ assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n");
+
+ // This will attempt to flush "partial", which will return Ok(0), which
+ // needs to be an error, because we've already informed the client
+ // that we accepted the write.
+ let err = writer.write(b" Line End\n").unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::WriteZero);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n");
+ }
+
+ /// Test that, if a write returns Ok(0) after a successful pre-flush, this
+ /// is propagated as Ok(0)
+ #[test]
+ fn line_buffer_write0_normal() {
+ let writer = ProgrammableSink {
+ // Accept two writes, then return Ok(0) on subsequent ones
+ max_writes: Some(2),
+
+ ..Default::default()
+ };
+ let mut writer = LineWriter::new(writer);
+
+ // This should write "Line 1\n" and buffer "Partial"
+ assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\n");
+
+ // This will flush partial, which will succeed, but then return Ok(0)
+ // when flushing " Line End\n"
+ assert_eq!(writer.write(b" Line End\n").unwrap(), 0);
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nPartial");
+ }
+
+ /// LineWriter has a custom `write_all`; make sure it works correctly
+ #[test]
+ fn line_write_all() {
+ let writer = ProgrammableSink {
+ // Only write 5 bytes at a time
+ accept_prefix: Some(5),
+ ..Default::default()
+ };
+ let mut writer = LineWriter::new(writer);
+
+ writer.write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial").unwrap();
+ assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\nLine 4\n");
+ writer.write_all(b" Line 5\n").unwrap();
+ assert_eq!(
+ writer.get_ref().buffer.as_slice(),
+ b"Line 1\nLine 2\nLine 3\nLine 4\nPartial Line 5\n".as_ref(),
+ );
+ }
+
+ #[test]
+ fn line_write_all_error() {
+ let writer = ProgrammableSink {
+ // Only accept up to 3 writes of up to 5 bytes each
+ accept_prefix: Some(5),
+ max_writes: Some(3),
+ ..Default::default()
+ };
+
+ let mut writer = LineWriter::new(writer);
+ let res = writer.write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial");
+ assert!(res.is_err());
+ // An error from write_all leaves everything in an indeterminate state,
+ // so there's nothing else to test here
+ }
+
+ /// Under certain circumstances, the old implementation of LineWriter
+ /// would try to buffer "to the last newline" but be forced to buffer
+ /// less than that, leading to inappropriate partial line writes.
+ /// Regression test for that issue.
+ #[test]
+ fn partial_multiline_buffering() {
+ let writer = ProgrammableSink {
+ // Write only up to 5 bytes at a time
+ accept_prefix: Some(5),
+ ..Default::default()
+ };
+
+ let mut writer = LineWriter::with_capacity(10, writer);
+
+ let content = b"AAAAABBBBB\nCCCCDDDDDD\nEEE";
+
+ // When content is written, LineWriter will try to write blocks A, B,
+ // C, and D. Only block A will succeed. Under the old behavior, LineWriter
+ // would then try to buffer B, C and D, but because its capacity is 10,
+ // it will only be able to buffer B and C. We don't want to buffer
+ // partial lines concurrent with whole lines, so the correct behavior
+ // is to buffer only block B (out to the newline)
+ assert_eq!(writer.write(content).unwrap(), 11);
+ assert_eq!(writer.get_ref().buffer, *b"AAAAA");
+
+ writer.flush().unwrap();
+ assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB\n");
+ }
+
+ /// Same as test_partial_multiline_buffering, but in the event NO full lines
+ /// fit in the buffer, just buffer as much as possible
+ #[test]
+ fn partial_multiline_buffering_without_full_line() {
+ let writer = ProgrammableSink {
+ // Write only up to 5 bytes at a time
+ accept_prefix: Some(5),
+ ..Default::default()
+ };
+
+ let mut writer = LineWriter::with_capacity(5, writer);
+
+ let content = b"AAAAABBBBBBBBBB\nCCCCC\nDDDDD";
+
+ // When content is written, LineWriter will try to write blocks A, B,
+ // and C. Only block A will succeed. Under the old behavior, LineWriter
+ // would then try to buffer B and C, but because its capacity is 5,
+ // it will only be able to buffer part of B. Because it's not possible
+ // for it to buffer any complete lines, it should buffer as much of B as
+ // possible
+ assert_eq!(writer.write(content).unwrap(), 10);
+ assert_eq!(writer.get_ref().buffer, *b"AAAAA");
+
+ writer.flush().unwrap();
+ assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB");
+ }
+
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ enum RecordedEvent {
+ Write(String),
+ Flush,
+ }
+
+ #[derive(Debug, Clone, Default)]
+ struct WriteRecorder {
+ pub events: Vec<RecordedEvent>,
+ }
+
+ impl Write for WriteRecorder {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ use crate::str::from_utf8;
+
+ self.events.push(RecordedEvent::Write(from_utf8(buf).unwrap().to_string()));
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.events.push(RecordedEvent::Flush);
+ Ok(())
+ }
+ }
+
+ /// Test that a normal, formatted writeln only results in a single write
+ /// call to the underlying writer. A naive implementation of
+ /// LineWriter::write_all results in two writes: one of the buffered data,
+ /// and another of the final substring in the formatted set
+ #[test]
+ fn single_formatted_write() {
+ let writer = WriteRecorder::default();
+ let mut writer = LineWriter::new(writer);
+
+ // Under a naive implementation of LineWriter, this will result in two
+ // writes: "hello, world" and "!\n", because write() has to flush the
+ // buffer before attempting to write the last "!\n". write_all shouldn't
+ // have this limitation.
+ writeln!(&mut writer, "{}, {}!", "hello", "world").unwrap();
+ assert_eq!(writer.get_ref().events, [RecordedEvent::Write("hello, world!\n".to_string())]);
+ }
}
// Don't drop this `SyncOnceCell`. We just moved out one of the fields, but didn't set
// the state to uninitialized.
- mem::ManuallyDrop::new(self);
+ mem::forget(self);
inner
}
#![feature(doc_cfg)]
#![feature(doc_keyword)]
#![feature(doc_masked)]
-#![cfg_attr(not(bootstrap), feature(doc_spotlight))]
+#![feature(doc_spotlight)]
#![feature(dropck_eyepatch)]
#![feature(duration_constants)]
#![feature(exact_size_is_empty)]
inner: c::sockaddr_in {
sin_family: c::AF_INET as c::sa_family_t,
sin_port: htons(port),
- sin_addr: *ip.as_inner(),
+ sin_addr: ip.into_inner(),
..unsafe { mem::zeroed() }
},
}
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn ip(&self) -> &Ipv4Addr {
+ // SAFETY: `Ipv4Addr` is `#[repr(C)] struct { _: in_addr; }`.
+ // It is safe to cast from `&in_addr` to `&Ipv4Addr`.
unsafe { &*(&self.inner.sin_addr as *const c::in_addr as *const Ipv4Addr) }
}
/// ```
#[stable(feature = "sockaddr_setters", since = "1.9.0")]
pub fn set_ip(&mut self, new_ip: Ipv4Addr) {
- self.inner.sin_addr = *new_ip.as_inner()
+ self.inner.sin_addr = new_ip.into_inner()
}
/// Returns the port number associated with this socket address.
use crate::io::Write as IoWrite;
use crate::mem::transmute;
use crate::sys::net::netc as c;
-use crate::sys_common::{AsInner, FromInner};
+use crate::sys_common::{AsInner, FromInner, IntoInner};
/// An IP address, either IPv4 or IPv6.
///
#[stable(feature = "rust1", since = "1.0.0")]
impl hash::Hash for Ipv4Addr {
fn hash<H: hash::Hasher>(&self, s: &mut H) {
- // `inner` is #[repr(packed)], so we need to copy `s_addr`.
+ // NOTE:
+ // * hash in big endian order
+ // * in netbsd, `in_addr` has `repr(packed)`, we need to
+ // copy `s_addr` to avoid unsafe borrowing
{ self.inner.s_addr }.hash(s)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Ord for Ipv4Addr {
fn cmp(&self, other: &Ipv4Addr) -> Ordering {
+ // Compare as native endian
u32::from_be(self.inner.s_addr).cmp(&u32::from_be(other.inner.s_addr))
}
}
-impl AsInner<c::in_addr> for Ipv4Addr {
- fn as_inner(&self) -> &c::in_addr {
- &self.inner
+impl IntoInner<c::in_addr> for Ipv4Addr {
+ fn into_inner(self) -> c::in_addr {
+ self.inner
}
}
#[test]
fn ipv4_addr_to_string() {
+ assert_eq!(Ipv4Addr::new(127, 0, 0, 1).to_string(), "127.0.0.1");
// Short address
assert_eq!(Ipv4Addr::new(1, 1, 1, 1).to_string(), "1.1.1.1");
// Long address
#[cfg(target_arch = "hexagon")]
mod arch {
- use crate::os::raw::{c_int, c_long, c_longlong, c_ulonglong};
+ use crate::os::raw::{c_int, c_long, c_uint};
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub type blkcnt_t = c_longlong;
+ pub type blkcnt_t = i64;
#[stable(feature = "raw_ext", since = "1.1.0")]
pub type blksize_t = c_long;
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub type ino_t = c_ulonglong;
+ pub type ino_t = u64;
#[stable(feature = "raw_ext", since = "1.1.0")]
pub type nlink_t = c_uint;
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub type off_t = c_longlong;
+ pub type off_t = i64;
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub type time_t = c_long;
+ pub type time_t = i64;
#[repr(C)]
#[derive(Clone)]
#[stable(feature = "raw_ext", since = "1.1.0")]
pub struct stat {
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_dev: ::dev_t,
+ pub st_dev: u64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_ino: ::c_ulonglong,
+ pub st_ino: u64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_mode: ::c_uint,
+ pub st_mode: u32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_nlink: ::c_uint,
+ pub st_nlink: u32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_uid: ::c_uint,
+ pub st_uid: u32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_gid: ::c_uint,
+ pub st_gid: u32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_rdev: ::c_ulonglong,
+ pub st_rdev: u64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub __pad1: ::c_ulong,
+ pub __pad1: u32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_size: ::c_longlong,
+ pub st_size: i64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_blksize: ::blksize_t,
+ pub st_blksize: i32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub __pad2: ::c_int,
+ pub __pad2: i32,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_blocks: ::blkcnt_t,
+ pub st_blocks: i64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_atime: ::time_t,
+ pub st_atime: i64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_atime_nsec: ::c_long,
+ pub st_atime_nsec: c_long,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_mtime: ::time_t,
+ pub st_mtime: i64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_mtime_nsec: ::c_long,
+ pub st_mtime_nsec: c_long,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_ctime: ::time_t,
+ pub st_ctime: i64,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub st_ctime_nsec: ::c_long,
+ pub st_ctime_nsec: c_long,
#[stable(feature = "raw_ext", since = "1.1.0")]
- pub __pad3: [::c_int; 2],
+ pub __pad3: [c_int; 2],
}
}
/// aborting the process as well. This function *only* catches unwinding panics,
/// not those that abort the process.
///
+/// Also note that unwinding into Rust code with a foreign exception (e.g. a
+/// an exception thrown from C++ code) is undefined behavior.
+///
/// # Examples
///
/// ```
rtabort!("Rust panics must be rethrown");
}
+/// This function is called by the panic runtime if it catches an exception
+/// object which does not correspond to a Rust panic.
+#[cfg(not(test))]
+#[rustc_std_internal_symbol]
+extern "C" fn __rust_foreign_exception() -> ! {
+ rtabort!("Rust cannot catch foreign exceptions");
+}
+
#[derive(Copy, Clone)]
enum Hook {
Default,
/// assert!(!bool_val);
/// ```
///
-/// [`assert!`]: macro.assert.html
-/// [`BitAnd`]: ops/trait.BitAnd.html
-/// [`BitOr`]: ops/trait.BitOr.html
-/// [`Not`]: ops/trait.Not.html
+/// [`BitAnd`]: ops::BitAnd
+/// [`BitOr`]: ops::BitOr
+/// [`Not`]: ops::Not
///
/// # Examples
///
/// }
/// ```
///
-/// Also, since `bool` implements the [`Copy`](marker/trait.Copy.html) trait, we don't
+/// Also, since `bool` implements the [`Copy`] trait, we don't
/// have to worry about the move semantics (just like the integer and float primitives).
///
/// Now an example of `bool` cast to integer type:
/// at all we know it can never produce a value which isn't a [`u32`]. This illustrates another
/// behaviour of the `!` type - expressions with type `!` will coerce into any other type.
///
-/// [`u32`]: primitive.str.html
-/// [`exit`]: process/fn.exit.html
+/// [`u32`]: prim@u32
+/// [`exit`]: process::exit
///
/// # `!` and generics
///
/// ever stops, it means that an error occurred. We don't even have to wrap the loop in an `Ok`
/// because `!` coerces to `Result<!, ConnectionError>` automatically.
///
-/// [`String::from_str`]: str/trait.FromStr.html#tymethod.from_str
-/// [`Result<String, !>`]: result/enum.Result.html
-/// [`Result<T, !>`]: result/enum.Result.html
-/// [`Result<!, E>`]: result/enum.Result.html
-/// [`Ok`]: result/enum.Result.html#variant.Ok
-/// [`String`]: string/struct.String.html
-/// [`Err`]: result/enum.Result.html#variant.Err
-/// [`FromStr`]: str/trait.FromStr.html
+/// [`String::from_str`]: str::FromStr::from_str
+/// [`Result<String, !>`]: Result
+/// [`Result<T, !>`]: Result
+/// [`Result<!, E>`]: Result
+/// [`String`]: string::String
+/// [`FromStr`]: str::FromStr
///
/// # `!` and traits
///
/// `impl` for this which simply panics, but the same is true for any type (we could `impl
/// Default` for (eg.) [`File`] by just making [`default()`] panic.)
///
-/// [`fmt::Result`]: fmt/type.Result.html
-/// [`File`]: fs/struct.File.html
-/// [`Debug`]: fmt/trait.Debug.html
-/// [`Default`]: default/trait.Default.html
-/// [`default()`]: default/trait.Default.html#tymethod.default
+/// [`File`]: fs::File
+/// [`Debug`]: fmt::Debug
+/// [`default()`]: Default::default
///
#[unstable(feature = "never_type", issue = "35121")]
mod prim_never {}
//
/// Raw, unsafe pointers, `*const T`, and `*mut T`.
///
-/// *[See also the `std::ptr` module](ptr/index.html).*
+/// *[See also the `std::ptr` module][`ptr`].*
///
/// Working with raw pointers in Rust is uncommon, typically limited to a few patterns.
/// Raw pointers can be unaligned or [`null`]. However, when a raw pointer is
/// but C APIs hand out a lot of pointers generally, so are a common source
/// of raw pointers in Rust.
///
-/// [`null`]: ../std/ptr/fn.null.html
-/// [`null_mut`]: ../std/ptr/fn.null_mut.html
+/// [`null`]: ptr::null
+/// [`null_mut`]: ptr::null_mut
/// [`is_null`]: ../std/primitive.pointer.html#method.is_null
/// [`offset`]: ../std/primitive.pointer.html#method.offset
-/// [`into_raw`]: ../std/boxed/struct.Box.html#method.into_raw
-/// [`drop`]: ../std/mem/fn.drop.html
-/// [`write`]: ../std/ptr/fn.write.html
+/// [`into_raw`]: Box::into_raw
+/// [`drop`]: mem::drop
+/// [`write`]: ptr::write
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_pointer {}
///
/// * A list with each element, i.e., `[x, y, z]`.
/// * A repeat expression `[x; N]`, which produces an array with `N` copies of `x`.
-/// The type of `x` must be [`Copy`][copy].
+/// The type of `x` must be [`Copy`].
///
/// Arrays of *any* size implement the following traits if the element type allows it:
///
-/// - [`Debug`][debug]
-/// - [`IntoIterator`][intoiterator] (implemented for `&[T; N]` and `&mut [T; N]`)
-/// - [`PartialEq`][partialeq], [`PartialOrd`][partialord], [`Eq`][eq], [`Ord`][ord]
-/// - [`Hash`][hash]
-/// - [`AsRef`][asref], [`AsMut`][asmut]
-/// - [`Borrow`][borrow], [`BorrowMut`][borrowmut]
+/// - [`Debug`]
+/// - [`IntoIterator`] (implemented for `&[T; N]` and `&mut [T; N]`)
+/// - [`PartialEq`], [`PartialOrd`], [`Eq`], [`Ord`]
+/// - [`Hash`]
+/// - [`AsRef`], [`AsMut`]
+/// - [`Borrow`], [`BorrowMut`]
///
-/// Arrays of sizes from 0 to 32 (inclusive) implement [`Default`][default] trait
+/// Arrays of sizes from 0 to 32 (inclusive) implement [`Default`] trait
/// if the element type allows it. As a stopgap, trait implementations are
/// statically generated up to size 32.
///
-/// Arrays of *any* size are [`Copy`][copy] if the element type is [`Copy`][copy]
-/// and [`Clone`][clone] if the element type is [`Clone`][clone]. This works
-/// because [`Copy`][copy] and [`Clone`][clone] traits are specially known
+/// Arrays of *any* size are [`Copy`] if the element type is [`Copy`]
+/// and [`Clone`] if the element type is [`Clone`]. This works
+/// because [`Copy`] and [`Clone`] traits are specially known
/// to the compiler.
///
/// Arrays coerce to [slices (`[T]`)][slice], so a slice method may be called on
/// Slices have a dynamic size and do not coerce to arrays.
///
/// You can move elements out of an array with a slice pattern. If you want
-/// one element, see [`mem::replace`][replace].
+/// one element, see [`mem::replace`].
///
/// # Examples
///
/// ```
///
/// [slice]: primitive.slice.html
-/// [copy]: marker/trait.Copy.html
-/// [clone]: clone/trait.Clone.html
-/// [debug]: fmt/trait.Debug.html
-/// [intoiterator]: iter/trait.IntoIterator.html
-/// [partialeq]: cmp/trait.PartialEq.html
-/// [partialord]: cmp/trait.PartialOrd.html
-/// [eq]: cmp/trait.Eq.html
-/// [ord]: cmp/trait.Ord.html
-/// [hash]: hash/trait.Hash.html
-/// [asref]: convert/trait.AsRef.html
-/// [asmut]: convert/trait.AsMut.html
-/// [borrow]: borrow/trait.Borrow.html
-/// [borrowmut]: borrow/trait.BorrowMut.html
-/// [default]: default/trait.Default.html
-/// [replace]: mem/fn.replace.html
-/// [`IntoIterator`]: iter/trait.IntoIterator.html
+/// [`Debug`]: fmt::Debug
+/// [`Hash`]: hash::Hash
+/// [`Borrow`]: borrow::Borrow
+/// [`BorrowMut`]: borrow::BorrowMut
///
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_array {}
/// means that elements are laid out so that every element is the same
/// distance from its neighbors.
///
-/// *[See also the `std::slice` module](slice/index.html).*
+/// *[See also the `std::slice` module][`crate::slice`].*
///
/// Slices are a view into a block of memory represented as a pointer and a
/// length.
//
/// String slices.
///
-/// *[See also the `std::str` module](str/index.html).*
+/// *[See also the `std::str` module][`crate::str`].*
///
/// The `str` type, also called a 'string slice', is the most primitive string
/// type. It is usually seen in its borrowed form, `&str`. It is also the type
/// assert_eq!(s, Ok(story));
/// ```
///
-/// [`as_ptr`]: #method.as_ptr
-/// [`len`]: #method.len
+/// [`as_ptr`]: str::as_ptr
+/// [`len`]: str::len
///
/// Note: This example shows the internals of `&str`. `unsafe` should not be
/// used to get a string slice under normal circumstances. Use `as_str`
/// * [`Default`]
/// * [`Hash`]
///
-/// [`Clone`]: clone/trait.Clone.html
-/// [`Copy`]: marker/trait.Copy.html
-/// [`PartialEq`]: cmp/trait.PartialEq.html
-/// [`Eq`]: cmp/trait.Eq.html
-/// [`PartialOrd`]: cmp/trait.PartialOrd.html
-/// [`Ord`]: cmp/trait.Ord.html
-/// [`Debug`]: fmt/trait.Debug.html
-/// [`Default`]: default/trait.Default.html
-/// [`Hash`]: hash/trait.Hash.html
+/// [`Debug`]: fmt::Debug
+/// [`Hash`]: hash::Hash
///
/// Due to a temporary restriction in Rust's type system, these traits are only
/// implemented on tuples of arity 12 or less. In the future, this may change.
///
/// For more information on floating point numbers, see [Wikipedia][wikipedia].
///
-/// *[See also the `std::f32::consts` module](f32/consts/index.html).*
+/// *[See also the `std::f32::consts` module][`crate::f32::consts`].*
///
/// [wikipedia]: https://en.wikipedia.org/wiki/Single-precision_floating-point_format
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(primitive = "f64")]
/// A 64-bit floating point type (specifically, the "binary64" type defined in IEEE 754-2008).
///
-/// This type is very similar to [`f32`](primitive.f32.html), but has increased
+/// This type is very similar to [`f32`], but has increased
/// precision by using twice as many bits. Please see [the documentation for
-/// `f32`](primitive.f32.html) or [Wikipedia on double precision
+/// `f32`][`f32`] or [Wikipedia on double precision
/// values][wikipedia] for more information.
///
-/// *[See also the `std::f64::consts` module](f64/consts/index.html).*
+/// *[See also the `std::f64::consts` module][`crate::f64::consts`].*
///
+/// [`f32`]: prim@f32
/// [wikipedia]: https://en.wikipedia.org/wiki/Double-precision_floating-point_format
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_f64 {}
/// implicit reference-pointer coercion and raw pointer equality via [`ptr::eq`], while
/// [`PartialEq`] compares values.
///
-/// [`ptr::eq`]: ptr/fn.eq.html
-/// [`PartialEq`]: cmp/trait.PartialEq.html
-///
/// ```
/// use std::ptr;
///
/// * [`Borrow`]
/// * [`Pointer`]
///
-/// [`Copy`]: marker/trait.Copy.html
-/// [`Clone`]: clone/trait.Clone.html
-/// [`Deref`]: ops/trait.Deref.html
-/// [`Borrow`]: borrow/trait.Borrow.html
-/// [`Pointer`]: fmt/trait.Pointer.html
+/// [`Deref`]: ops::Deref
+/// [`Borrow`]: borrow::Borrow
+/// [`Pointer`]: fmt::Pointer
///
/// `&mut T` references get all of the above except `Copy` and `Clone` (to prevent creating
/// multiple simultaneous mutable borrows), plus the following, regardless of the type of its
/// * [`DerefMut`]
/// * [`BorrowMut`]
///
-/// [`DerefMut`]: ops/trait.DerefMut.html
-/// [`BorrowMut`]: borrow/trait.BorrowMut.html
+/// [`DerefMut`]: ops::DerefMut
+/// [`BorrowMut`]: borrow::BorrowMut
///
/// The following traits are implemented on `&T` references if the underlying `T` also implements
/// that trait:
/// * [`Hash`]
/// * [`ToSocketAddrs`]
///
-/// [`std::fmt`]: fmt/index.html
-/// [`fmt::Write`]: fmt/trait.Write.html
-/// [`PartialOrd`]: cmp/trait.PartialOrd.html
-/// [`Ord`]: cmp/trait.Ord.html
-/// [`PartialEq`]: cmp/trait.PartialEq.html
-/// [`Eq`]: cmp/trait.Eq.html
-/// [`AsRef`]: convert/trait.AsRef.html
-/// [`Fn`]: ops/trait.Fn.html
-/// [`FnMut`]: ops/trait.FnMut.html
-/// [`FnOnce`]: ops/trait.FnOnce.html
-/// [`Hash`]: hash/trait.Hash.html
-/// [`ToSocketAddrs`]: net/trait.ToSocketAddrs.html
+/// [`std::fmt`]: fmt
+/// ['Pointer`]: fmt::Pointer
+/// [`Hash`]: hash::Hash
+/// [`ToSocketAddrs`]: net::ToSocketAddrs
///
/// `&mut T` references get all of the above except `ToSocketAddrs`, plus the following, if `T`
/// implements that trait:
/// * [`Seek`]
/// * [`BufRead`]
///
-/// [`AsMut`]: convert/trait.AsMut.html
-/// [`Iterator`]: iter/trait.Iterator.html
-/// [`DoubleEndedIterator`]: iter/trait.DoubleEndedIterator.html
-/// [`ExactSizeIterator`]: iter/trait.ExactSizeIterator.html
-/// [`FusedIterator`]: iter/trait.FusedIterator.html
-/// [`TrustedLen`]: iter/trait.TrustedLen.html
-/// [`Send`]: marker/trait.Send.html
-/// [`io::Write`]: io/trait.Write.html
-/// [`Read`]: io/trait.Read.html
-/// [`Seek`]: io/trait.Seek.html
-/// [`BufRead`]: io/trait.BufRead.html
+/// [`FusedIterator`]: iter::FusedIterator
+/// [`TrustedLen`]: iter::TrustedLen
+/// [`Seek`]: io::Seek
+/// [`BufRead`]: io::BufRead
+/// [`Read`]: io::Read
///
/// Note that due to method call deref coercion, simply calling a trait method will act like they
/// work on references as well as they do on owned values! The implementations described here are
///
/// *See also the traits [`Fn`], [`FnMut`], and [`FnOnce`].*
///
-/// [`Fn`]: ops/trait.Fn.html
-/// [`FnMut`]: ops/trait.FnMut.html
-/// [`FnOnce`]: ops/trait.FnOnce.html
+/// [`Fn`]: ops::Fn
+/// [`FnMut`]: ops::FnMut
+/// [`FnOnce`]: ops::FnOnce
///
/// Function pointers are pointers that point to *code*, not data. They can be called
/// just like functions. Like references, function pointers are, among other things, assumed to
/// * [`Pointer`]
/// * [`Debug`]
///
-/// [`Clone`]: clone/trait.Clone.html
-/// [`PartialEq`]: cmp/trait.PartialEq.html
-/// [`Eq`]: cmp/trait.Eq.html
-/// [`PartialOrd`]: cmp/trait.PartialOrd.html
-/// [`Ord`]: cmp/trait.Ord.html
-/// [`Hash`]: hash/trait.Hash.html
-/// [`Pointer`]: fmt/trait.Pointer.html
-/// [`Debug`]: fmt/trait.Debug.html
+/// [`Hash`]: hash::Hash
+/// [`Pointer`]: fmt::Pointer
///
/// Due to a temporary restriction in Rust's type system, these traits are only implemented on
/// functions that take 12 arguments or less, with the `"Rust"` and `"C"` ABIs. In the future, this
/// In addition, function pointers of *any* signature, ABI, or safety are [`Copy`], and all *safe*
/// function pointers implement [`Fn`], [`FnMut`], and [`FnOnce`]. This works because these traits
/// are specially known to the compiler.
-///
-/// [`Copy`]: marker/trait.Copy.html
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_fn {}
}
#[inline]
- fn is_read_vectored(&self) -> bool {
+ pub fn is_read_vectored(&self) -> bool {
true
}
}
fn exited(&self) -> bool {
- /*unsafe*/
- { libc::WIFEXITED(self.0) }
+ libc::WIFEXITED(self.0)
}
pub fn success(&self) -> bool {
}
pub fn code(&self) -> Option<i32> {
- if self.exited() {
- Some(/*unsafe*/ { libc::WEXITSTATUS(self.0) })
- } else {
- None
- }
+ if self.exited() { Some(libc::WEXITSTATUS(self.0)) } else { None }
}
pub fn signal(&self) -> Option<i32> {
- if !self.exited() {
- Some(/*unsafe*/ { libc::WTERMSIG(self.0) })
- } else {
- None
- }
+ if !self.exited() { Some(libc::WTERMSIG(self.0)) } else { None }
}
}
#![unstable(feature = "thread_local_internals", issue = "none")]
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
- use crate::sys_common::thread_local::register_dtor_fallback;
+ use crate::sys_common::thread_local_dtor::register_dtor_fallback;
register_dtor_fallback(t, dtor);
}
use crate::sys_common::{AsInner, AsInnerMut, FromInner};
/// WASI-specific extensions to [`File`].
-///
-/// [`File`]: ../../../../std/fs/struct.File.html
pub trait FileExt {
/// Reads a number of bytes starting from a given offset.
///
///
/// Note that similar to [`File::read`], it is not an error to return with a
/// short read.
- ///
- /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
let bufs = &mut [IoSliceMut::new(buf)];
self.read_vectored_at(bufs, offset)
///
/// Note that similar to [`File::read_vectored`], it is not an error to
/// return with a short read.
- ///
- /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read_vectored
fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
/// Reads the exact number of byte required to fill `buf` from the given offset.
///
/// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
///
- /// [`Read::read_exact`]: ../../../../std/io/trait.Read.html#method.read_exact
- /// [`read_at`]: #tymethod.read_at
+ /// [`read_at`]: FileExt::read_at
///
/// # Errors
///
/// If this function returns an error, it is unspecified how many bytes it
/// has read, but it will never read more than would be necessary to
/// completely fill the buffer.
- ///
- /// [`ErrorKind::Interrupted`]: ../../../../std/io/enum.ErrorKind.html#variant.Interrupted
- /// [`ErrorKind::UnexpectedEof`]: ../../../../std/io/enum.ErrorKind.html#variant.UnexpectedEof
#[stable(feature = "rw_exact_all_at", since = "1.33.0")]
fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
while !buf.is_empty() {
///
/// Note that similar to [`File::write`], it is not an error to return a
/// short write.
- ///
- /// [`File::write`]: ../../../../std/fs/struct.File.html#write.v
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
let bufs = &[IoSlice::new(buf)];
self.write_vectored_at(bufs, offset)
///
/// Note that similar to [`File::write_vectored`], it is not an error to return a
/// short write.
- ///
- /// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored
fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
/// Attempts to write an entire buffer starting from a given offset.
/// This function will return the first error of
/// non-[`ErrorKind::Interrupted`] kind that [`write_at`] returns.
///
- /// [`ErrorKind::Interrupted`]: ../../../../std/io/enum.ErrorKind.html#variant.Interrupted
- /// [`write_at`]: #tymethod.write_at
+ /// [`write_at`]: FileExt::write_at
#[stable(feature = "rw_exact_all_at", since = "1.33.0")]
fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
while !buf.is_empty() {
}
/// WASI-specific extensions to [`fs::OpenOptions`].
-///
-/// [`fs::OpenOptions`]: ../../../../std/fs/struct.OpenOptions.html
pub trait OpenOptionsExt {
/// Pass custom `dirflags` argument to `path_open`.
///
}
/// WASI-specific extensions to [`fs::Metadata`].
-///
-/// [`fs::Metadata`]: ../../../../std/fs/struct.Metadata.html
pub trait MetadataExt {
/// Returns the `st_dev` field of the internal `filestat_t`
fn dev(&self) -> u64;
///
/// Adds support for special WASI file types such as block/character devices,
/// pipes, and sockets.
-///
-/// [`FileType`]: ../../../../std/fs/struct.FileType.html
pub trait FileTypeExt {
/// Returns `true` if this file type is a block device.
fn is_block_device(&self) -> bool;
}
/// WASI-specific extension methods for [`fs::DirEntry`].
-///
-/// [`fs::DirEntry`]: ../../../../std/fs/struct.DirEntry.html
pub trait DirEntryExt {
/// Returns the underlying `d_ino` field of the `dirent_t`
fn ino(&self) -> u64;
.unwrap_or(c::INFINITE)
}
-// On Windows, use the processor-specific __fastfail mechanism. In Windows 8
-// and later, this will terminate the process immediately without running any
-// in-process exception handlers. In earlier versions of Windows, this
-// sequence of instructions will be treated as an access violation,
-// terminating the process but without necessarily bypassing all exception
-// handlers.
-//
-// https://docs.microsoft.com/en-us/cpp/intrinsics/fastfail
+/// Use `__fastfail` to abort the process
+///
+/// This is the same implementation as in libpanic_abort's `__rust_start_panic`. See
+/// that function for more information on `__fastfail`
#[allow(unreachable_code)]
pub fn abort_internal() -> ! {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> {
let mreq = c::ip_mreq {
- imr_multiaddr: *multiaddr.as_inner(),
- imr_interface: *interface.as_inner(),
+ imr_multiaddr: multiaddr.into_inner(),
+ imr_interface: interface.into_inner(),
};
setsockopt(&self.inner, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreq)
}
pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> {
let mreq = c::ip_mreq {
- imr_multiaddr: *multiaddr.as_inner(),
- imr_interface: *interface.as_inner(),
+ imr_multiaddr: multiaddr.into_inner(),
+ imr_interface: interface.into_inner(),
};
setsockopt(&self.inner, c::IPPROTO_IP, c::IP_DROP_MEMBERSHIP, mreq)
}
}
/// Platform-specific extensions to [`OsString`].
-///
-/// [`OsString`]: ../../../../std/ffi/struct.OsString.html
#[stable(feature = "rust1", since = "1.0.0")]
pub trait OsStringExt {
/// Creates an [`OsString`] from a byte vector.
///
/// See the module documentation for an example.
- ///
- /// [`OsString`]: ../../../ffi/struct.OsString.html
#[stable(feature = "rust1", since = "1.0.0")]
fn from_vec(vec: Vec<u8>) -> Self;
/// Yields the underlying byte vector of this [`OsString`].
///
/// See the module documentation for an example.
- ///
- /// [`OsString`]: ../../../ffi/struct.OsString.html
#[stable(feature = "rust1", since = "1.0.0")]
fn into_vec(self) -> Vec<u8>;
}
}
/// Platform-specific extensions to [`OsStr`].
-///
-/// [`OsStr`]: ../../../../std/ffi/struct.OsStr.html
#[stable(feature = "rust1", since = "1.0.0")]
pub trait OsStrExt {
#[stable(feature = "rust1", since = "1.0.0")]
/// Creates an [`OsStr`] from a byte slice.
///
/// See the module documentation for an example.
- ///
- /// [`OsStr`]: ../../../ffi/struct.OsStr.html
fn from_bytes(slice: &[u8]) -> &Self;
/// Gets the underlying byte view of the [`OsStr`] slice.
///
/// See the module documentation for an example.
- ///
- /// [`OsStr`]: ../../../ffi/struct.OsStr.html
#[stable(feature = "rust1", since = "1.0.0")]
fn as_bytes(&self) -> &[u8];
}
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::thread;
+#[allow(unused_imports)] // for intra-doc links
+use crate::sync::{Mutex, RwLock};
+
pub struct Flag {
failed: AtomicBool,
}
/// }
/// };
/// ```
-///
-/// [`Mutex`]: ../../std/sync/struct.Mutex.html
-/// [`RwLock`]: ../../std/sync/struct.RwLock.html
#[stable(feature = "rust1", since = "1.0.0")]
pub struct PoisonError<T> {
guard: T,
/// can occur while trying to acquire a lock, from the [`try_lock`] method on a
/// [`Mutex`] or the [`try_read`] and [`try_write`] methods on an [`RwLock`].
///
-/// [`Mutex`]: struct.Mutex.html
-/// [`RwLock`]: struct.RwLock.html
-/// [`TryLockResult`]: type.TryLockResult.html
-/// [`try_lock`]: struct.Mutex.html#method.try_lock
-/// [`try_read`]: struct.RwLock.html#method.try_read
-/// [`try_write`]: struct.RwLock.html#method.try_write
+/// [`try_lock`]: Mutex::try_lock
+/// [`try_read`]: RwLock::try_read
+/// [`try_write`]: RwLock::try_write
#[stable(feature = "rust1", since = "1.0.0")]
pub enum TryLockError<T> {
/// The lock could not be acquired because another thread failed while holding
/// the associated guard, and it can be acquired through the [`into_inner`]
/// method.
///
-/// [`Ok`]: ../../std/result/enum.Result.html#variant.Ok
-/// [`Err`]: ../../std/result/enum.Result.html#variant.Err
-/// [`into_inner`]: ../../std/sync/struct.PoisonError.html#method.into_inner
+/// [`into_inner`]: PoisonError::into_inner
#[stable(feature = "rust1", since = "1.0.0")]
pub type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;
/// For more information, see [`LockResult`]. A `TryLockResult` doesn't
/// necessarily hold the associated guard in the [`Err`] type as the lock may not
/// have been acquired for other reasons.
-///
-/// [`LockResult`]: ../../std/sync/type.LockResult.html
-/// [`Err`]: ../../std/result/enum.Result.html#variant.Err
#[stable(feature = "rust1", since = "1.0.0")]
pub type TryLockResult<Guard> = Result<Guard, TryLockError<Guard>>;
/// Creates a `PoisonError`.
///
/// This is generally created by methods like [`Mutex::lock`] or [`RwLock::read`].
- ///
- /// [`Mutex::lock`]: ../../std/sync/struct.Mutex.html#method.lock
- /// [`RwLock::read`]: ../../std/sync/struct.RwLock.html#method.read
#[stable(feature = "sync_poison", since = "1.2.0")]
pub fn new(guard: T) -> PoisonError<T> {
PoisonError { guard }
/// | DARWIN | [gettimeofday] |
/// | VXWorks | [clock_gettime (Realtime Clock)] |
/// | WASI | [__wasi_clock_time_get (Realtime Clock)] |
-/// | Windows | [GetSystemTimeAsFileTime] |
+/// | Windows | [GetSystemTimePreciseAsFileTime] / [GetSystemTimeAsFileTime] |
///
/// [clock_time_get (Realtime Clock)]: https://nuxi.nl/cloudabi/#clock_time_get
/// [`insecure_time` usercall]: https://edp.fortanix.com/docs/api/fortanix_sgx_abi/struct.Usercalls.html#method.insecure_time
/// [gettimeofday]: http://man7.org/linux/man-pages/man2/gettimeofday.2.html
/// [clock_gettime (Realtime Clock)]: https://linux.die.net/man/3/clock_gettime
/// [__wasi_clock_time_get (Realtime Clock)]: https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/docs.md#clock_time_get
+/// [GetSystemTimePreciseAsFileTime]: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
/// [GetSystemTimeAsFileTime]: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime
///
/// **Disclaimer:** These system calls might change over time.
config.save_toolstates = None;
config.skip_only_host_steps = false;
config.dry_run = true;
+ config.ninja = false;
// try to avoid spurious failures in dist where we create/delete each others file
let dir = config
.out
false
};
- if cxx_configured {
+ // for VxWorks, record CXX compiler which will be used in lib.rs:linker()
+ if cxx_configured || target.contains("vxworks") {
let compiler = cfg.get_compiler();
build.cxx.insert(target, compiler);
}
use crate::Build;
// The version number
-pub const CFG_RELEASE_NUM: &str = "1.47.0";
+pub const CFG_RELEASE_NUM: &str = "1.48.0";
pub struct GitInfo {
inner: Option<Info>,
pub fn default_opts() -> Config {
let mut config = Config::default();
config.llvm_optimize = true;
+ config.ninja = true;
config.llvm_version_check = true;
config.backtrace = true;
config.rust_optimize = true;
let idx = line.find(':').unwrap();
let key = &line[..idx];
let trim_chars: &[_] = &[' ', '='];
- let value = line[(idx + 1)..].trim_start_matches(trim_chars).split(';').map(PathBuf::from);
+ let value = env::split_paths(line[(idx + 1)..].trim_start_matches(trim_chars));
if key == "programs" {
bin_path.extend(value);
if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.as_ref())
{
Some(linker)
+ } else if target.contains("vxworks") {
+ // need to use CXX compiler as linker to resolve the exception functions
+ // that are only existed in CXX libraries
+ Some(self.cxx[&target].path())
} else if target != self.config.build
&& util::use_host_linker(target)
&& !target.contains("msvc")
.define("LLVM_TARGET_ARCH", target_native.split('-').next().unwrap())
.define("LLVM_DEFAULT_TARGET_TRIPLE", target_native);
- if !target.contains("netbsd") && target != "aarch64-apple-darwin" {
+ if target != "aarch64-apple-darwin" {
cfg.define("LLVM_ENABLE_ZLIB", "ON");
} else {
- // FIXME: Enable zlib on NetBSD too
- // https://github.com/rust-lang/rust/pull/72696#issuecomment-641517185
cfg.define("LLVM_ENABLE_ZLIB", "OFF");
}
if build.config.ninja {
// Some Linux distros rename `ninja` to `ninja-build`.
// CMake can work with either binary name.
- if cmd_finder.maybe_have("ninja-build").is_none() {
- cmd_finder.must_have("ninja");
+ if cmd_finder.maybe_have("ninja-build").is_none()
+ && cmd_finder.maybe_have("ninja").is_none()
+ {
+ eprintln!(
+ "
+Couldn't find required command: ninja
+You should install ninja, or set ninja=false in config.toml
+"
+ );
+ std::process::exit(1);
}
}
cargo.arg("--quiet");
}
+ if builder.config.cmd.bless() {
+ // Bless `expect!` tests.
+ cargo.env("UPDATE_EXPECT", "1");
+ }
+
if target.contains("emscripten") {
cargo.env(
format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
# version that we're using, 8.2, cannot compile LLVM for OSX 10.7.
x86_64-apple:
SCRIPT: ./x.py --stage 2 test
- INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc
+ INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.8
MACOSX_STD_DEPLOYMENT_TARGET: 10.7
dist-x86_64-apple:
SCRIPT: ./x.py dist
- INITIAL_RUST_CONFIGURE_ARGS: --target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc
+ INITIAL_RUST_CONFIGURE_ARGS: --target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
dist-x86_64-apple-alt:
SCRIPT: ./x.py dist
- INITIAL_RUST_CONFIGURE_ARGS: --enable-extended --enable-profiler --set rust.jemalloc
+ INITIAL_RUST_CONFIGURE_ARGS: --enable-extended --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
libc6-dev \
libc6-dev-armhf-cross \
make \
+ ninja-build \
python3 \
qemu-system-arm \
xz-utils
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
libcurl4-openssl-dev \
libssl-dev \
make \
+ ninja-build \
nasm \
pkg-config \
python3 \
libc6-dev \
libc6-dev-riscv64-cross \
make \
+ ninja-build \
patch \
python3 \
qemu-system-misc \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++-multilib \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
clang \
make \
+ ninja-build \
file \
curl \
ca-certificates \
--enable-profiler \
--set target.i686-unknown-linux-gnu.linker=clang \
--build=i686-unknown-linux-gnu \
+ --set llvm.ninja=false \
--set rust.jemalloc
ENV SCRIPT python2.7 ../x.py dist --build $HOSTS --host $HOSTS --target $HOSTS
ENV CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=clang
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
libncurses-dev \
gawk \
make \
+ ninja-build \
file \
curl \
ca-certificates \
git \
lld-5.0 \
make \
+ ninja-build \
python \
sudo \
xz-utils
RUN apt-get update && apt-get install -y --no-install-recommends \
clang \
make \
+ ninja-build \
file \
curl \
ca-certificates \
--set target.x86_64-unknown-linux-gnu.ar=/rustroot/bin/llvm-ar \
--set target.x86_64-unknown-linux-gnu.ranlib=/rustroot/bin/llvm-ranlib \
--set llvm.thin-lto=true \
+ --set llvm.ninja=false \
--set rust.jemalloc
ENV SCRIPT python2.7 ../x.py dist --host $HOSTS --target $HOSTS
ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=clang
source shared.sh
-LLVM=llvmorg-9.0.0
+LLVM=llvmorg-10.0.0
mkdir llvm-project
cd llvm-project
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
wget \
curl \
ENV RUST_CONFIGURE_ARGS \
--musl-root-x86_64=/usr/local/x86_64-linux-musl \
--enable-extended \
+ --enable-profiler \
--enable-lld \
--set target.x86_64-unknown-linux-musl.crt-static=false \
--build $HOSTS
RUN apt-get update && apt-get install -y --no-install-recommends \
g++-multilib \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++-multilib \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
g++ \
g++-arm-linux-gnueabi \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
make \
+ ninja-build \
file \
curl \
ca-certificates \
git \
libssl-dev \
make \
+ ninja-build \
pkg-config \
python3 \
sudo \
libssl-dev \
libtool-bin \
make \
+ ninja-build \
patch \
pkg-config \
python3 \
with:
fetch-depth: 2
- - name: configure GitHub Actions to kill the build when outdated
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
- with:
- github_token: "${{ secrets.github_token }}"
- if: success() && !env.SKIP_JOB && github.ref != 'refs/heads/try'
- <<: *step
-
# Rust Log Analyzer can't currently detect the PR number of a GitHub
# Actions build on its own, so a hint in the log message is needed to
# point it in the right direction.
run: src/ci/scripts/should-skip-this.sh
<<: *step
+ - name: configure GitHub Actions to kill the build when outdated
+ uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
+ with:
+ github_token: "${{ secrets.github_token }}"
+ # TODO: remove the condition on RUST_CI_TEMP_SKIP_CANCEL_OUTDATED once
+ # we remove the `auto-fallible` job.
+ if: success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && !env.RUST_CI_TEMP_SKIP_CANCEL_OUTDATED
+ <<: *step
+
- name: collect CPU statistics
run: src/ci/scripts/collect-cpu-stats.sh
<<: *step
- name: dist-x86_64-apple
env:
SCRIPT: ./x.py dist
- RUST_CONFIGURE_ARGS: --target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc
+ RUST_CONFIGURE_ARGS: --target=aarch64-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
DIST_REQUIRE_ALL_TOOLS: 1
+
+ # TODO: remove once we move this job away from auto-fallible.
+ # Also, remove the variable from the cancel-outdated-builds step
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
<<: *job-macos-xl
- name: dist-x86_64-apple-alt
env:
SCRIPT: ./x.py dist
- RUST_CONFIGURE_ARGS: --enable-extended --enable-profiler --set rust.jemalloc
+ RUST_CONFIGURE_ARGS: --enable-extended --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
+
+ # TODO: remove once we move this job away from auto-fallible.
+ # Also, remove the variable from the cancel-outdated-builds step
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
<<: *job-macos-xl
- name: x86_64-apple
env:
SCRIPT: ./x.py --stage 2 test
- RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc
+ RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
MACOSX_DEPLOYMENT_TARGET: 10.8
MACOSX_STD_DEPLOYMENT_TARGET: 10.7
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
+
+ # TODO: remove once we move this job away from auto-fallible.
+ # Also, remove the variable from the cancel-outdated-builds step
+ RUST_CI_TEMP_SKIP_CANCEL_OUTDATED: 1
<<: *job-macos-xl
master:
source "$(cd "$(dirname "$0")" && pwd)/../shared.sh"
+# Update both macOS's and Windows's tarballs when bumping the version here.
+LLVM_VERSION="10.0.0"
+
if isMacOS; then
- curl -f "${MIRRORS_BASE}/clang%2Bllvm-9.0.0-x86_64-darwin-apple.tar.xz" | tar xJf -
+ curl -f "${MIRRORS_BASE}/clang%2Bllvm-${LLVM_VERSION}-x86_64-apple-darwin.tar.xz" | tar xJf -
- ciCommandSetEnv CC "$(pwd)/clang+llvm-9.0.0-x86_64-darwin-apple/bin/clang"
- ciCommandSetEnv CXX "$(pwd)/clang+llvm-9.0.0-x86_64-darwin-apple/bin/clang++"
+ ciCommandSetEnv CC "$(pwd)/clang+llvm-${LLVM_VERSION}-x86_64-apple-darwin/bin/clang"
+ ciCommandSetEnv CXX "$(pwd)/clang+llvm-${LLVM_VERSION}-x86_64-apple-darwin/bin/clang++"
# macOS 10.15 onwards doesn't have libraries in /usr/include anymore: those
# are now located deep into the filesystem, under Xcode's own files. The
#
# Note that the LLVM installer is an NSIS installer
#
- # Original downloaded here came from
- # http://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe
+ # Original downloaded here came from:
+ #
+ # https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe
+ #
# That installer was run through `wine ./installer.exe /S /NCRC` on Linux
# and then the resulting installation directory (found in
# `$HOME/.wine/drive_c/Program Files/LLVM`) was packaged up into a tarball.
mkdir -p citools
cd citools
- curl -f "${MIRRORS_BASE}/LLVM-9.0.0-win64.tar.gz" | tar xzf -
+ curl -f "${MIRRORS_BASE}/LLVM-${LLVM_VERSION}-win64.tar.gz" | tar xzf -
ciCommandSetEnv RUST_CONFIGURE_ARGS \
"${RUST_CONFIGURE_ARGS} --set llvm.clang-cl=$(pwd)/clang-rust/bin/clang-cl.exe"
fi
`armv7-wrs-vxworks-eabihf` | ? | |
`armv7a-none-eabihf` | * | | ARM Cortex-A, hardfloat
`armv7s-apple-ios` | ✓[^apple] | |
-`avr-unknown-unknown` | ? | | AVR
+`avr-unknown-gnu-atmega328` | ✗ | | AVR. Requires `-Z build-std=core`
`hexagon-unknown-linux-musl` | ? | |
`i386-apple-ios` | ✓[^apple] | | 32-bit x86 iOS
`i686-apple-darwin` | ✓ | ✓ | 32-bit OSX (10.7+, Lion+)
hir::GenericBound::LangItemTrait(
// ::std::future::Future<future_params>
- hir::LangItem::FutureTraitLangItem,
+ hir::LangItem::Future,
span,
self.next_id(),
future_args,
[dependencies]
bitflags = "1.0"
-flate2 = "1.0"
libc = "0.2"
measureme = "0.7.1"
+snap = "1"
tracing = "0.1"
rustc_middle = { path = "../librustc_middle" }
rustc-demangle = "0.1"
format!("{{{}{}}}", class, idx)
} else if reg == InlineAsmReg::AArch64(AArch64InlineAsmReg::x30) {
// LLVM doesn't recognize x30
- "lr".to_string()
+ "{lr}".to_string()
} else {
format!("{{{}}}", reg.name())
}
metadata: &EncodedMetadata,
llvm_module: &mut ModuleLlvm,
) {
- use flate2::write::DeflateEncoder;
- use flate2::Compression;
+ use snap::write::FrameEncoder;
use std::io::Write;
let (metadata_llcx, metadata_llmod) = (&*llvm_module.llcx, llvm_module.llmod());
let mut compressed = tcx.metadata_encoding_version();
- DeflateEncoder::new(&mut compressed, Compression::fast())
- .write_all(&metadata.raw_data)
- .unwrap();
+ FrameEncoder::new(&mut compressed).write_all(&metadata.raw_data).unwrap();
let llmeta = common::bytes_in_context(metadata_llcx, &compressed);
let llconst = common::struct_in_context(metadata_llcx, &[llmeta], false);
pub dbg_cx: Option<debuginfo::CrateDebugContext<'ll, 'tcx>>,
eh_personality: Cell<Option<&'ll Value>>,
+ eh_catch_typeinfo: Cell<Option<&'ll Value>>,
pub rust_try_fn: Cell<Option<&'ll Value>>,
intrinsics: RefCell<FxHashMap<&'static str, &'ll Value>>,
coverage_cx,
dbg_cx,
eh_personality: Cell::new(None),
+ eh_catch_typeinfo: Cell::new(None),
rust_try_fn: Cell::new(None),
intrinsics: Default::default(),
local_gen_sym_counter: Cell::new(0),
}
None
}
+
+ crate fn eh_catch_typeinfo(&self) -> &'b Value {
+ if let Some(eh_catch_typeinfo) = self.eh_catch_typeinfo.get() {
+ return eh_catch_typeinfo;
+ }
+ let tcx = self.tcx;
+ assert!(self.sess().target.target.options.is_like_emscripten);
+ let eh_catch_typeinfo = match tcx.lang_items().eh_catch_typeinfo() {
+ Some(def_id) => self.get_static(def_id),
+ _ => {
+ let ty = self
+ .type_struct(&[self.type_ptr_to(self.type_isize()), self.type_i8p()], false);
+ self.declare_global("rust_eh_catch_typeinfo", ty)
+ }
+ };
+ let eh_catch_typeinfo = self.const_bitcast(eh_catch_typeinfo, self.type_i8p());
+ self.eh_catch_typeinfo.set(Some(eh_catch_typeinfo));
+ eh_catch_typeinfo
+ }
}
impl<'b, 'tcx> CodegenCx<'b, 'tcx> {
bx.store(bx.const_i32(0), dest, ret_align);
} else if wants_msvc_seh(bx.sess()) {
codegen_msvc_try(bx, try_func, data, catch_func, dest);
+ } else if bx.sess().target.target.options.is_like_emscripten {
+ codegen_emcc_try(bx, try_func, data, catch_func, dest);
} else {
codegen_gnu_try(bx, try_func, data, catch_func, dest);
}
let mut normal = bx.build_sibling_block("normal");
let mut catchswitch = bx.build_sibling_block("catchswitch");
- let mut catchpad = bx.build_sibling_block("catchpad");
+ let mut catchpad_rust = bx.build_sibling_block("catchpad_rust");
+ let mut catchpad_foreign = bx.build_sibling_block("catchpad_foreign");
let mut caught = bx.build_sibling_block("caught");
let try_func = llvm::get_param(bx.llfn(), 0);
// We're generating an IR snippet that looks like:
//
// declare i32 @rust_try(%try_func, %data, %catch_func) {
- // %slot = alloca u8*
+ // %slot = alloca i8*
// invoke %try_func(%data) to label %normal unwind label %catchswitch
//
// normal:
// ret i32 0
//
// catchswitch:
- // %cs = catchswitch within none [%catchpad] unwind to caller
+ // %cs = catchswitch within none [%catchpad_rust, %catchpad_foreign] unwind to caller
//
- // catchpad:
- // %tok = catchpad within %cs [%type_descriptor, 0, %slot]
+ // catchpad_rust:
+ // %tok = catchpad within %cs [%type_descriptor, 8, %slot]
// %ptr = load %slot
// call %catch_func(%data, %ptr)
// catchret from %tok to label %caught
//
+ // catchpad_foreign:
+ // %tok = catchpad within %cs [null, 64, null]
+ // call %catch_func(%data, null)
+ // catchret from %tok to label %caught
+ //
// caught:
// ret i32 1
// }
// This structure follows the basic usage of throw/try/catch in LLVM.
// For example, compile this C++ snippet to see what LLVM generates:
//
- // #include <stdint.h>
- //
// struct rust_panic {
// rust_panic(const rust_panic&);
// ~rust_panic();
//
- // uint64_t x[2];
+ // void* x[2];
// };
//
// int __rust_try(
// } catch(rust_panic& a) {
// catch_func(data, &a);
// return 1;
+ // } catch(...) {
+ // catch_func(data, NULL);
+ // return 1;
// }
// }
//
normal.ret(bx.const_i32(0));
- let cs = catchswitch.catch_switch(None, None, 1);
- catchswitch.add_handler(cs, catchpad.llbb());
+ let cs = catchswitch.catch_switch(None, None, 2);
+ catchswitch.add_handler(cs, catchpad_rust.llbb());
+ catchswitch.add_handler(cs, catchpad_foreign.llbb());
// We can't use the TypeDescriptor defined in libpanic_unwind because it
// might be in another DLL and the SEH encoding only supports specifying
//
// Source: MicrosoftCXXABI::getAddrOfCXXCatchHandlerType in clang
let flags = bx.const_i32(8);
- let funclet = catchpad.catch_pad(cs, &[tydesc, flags, slot]);
- let ptr = catchpad.load(slot, ptr_align);
- catchpad.call(catch_func, &[data, ptr], Some(&funclet));
-
- catchpad.catch_ret(&funclet, caught.llbb());
+ let funclet = catchpad_rust.catch_pad(cs, &[tydesc, flags, slot]);
+ let ptr = catchpad_rust.load(slot, ptr_align);
+ catchpad_rust.call(catch_func, &[data, ptr], Some(&funclet));
+ catchpad_rust.catch_ret(&funclet, caught.llbb());
+
+ // The flag value of 64 indicates a "catch-all".
+ let flags = bx.const_i32(64);
+ let null = bx.const_null(bx.type_i8p());
+ let funclet = catchpad_foreign.catch_pad(cs, &[null, flags, null]);
+ catchpad_foreign.call(catch_func, &[data, null], Some(&funclet));
+ catchpad_foreign.catch_ret(&funclet, caught.llbb());
caught.ret(bx.const_i32(1));
});
// rust_try ignores the selector.
let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false);
let vals = catch.landing_pad(lpad_ty, bx.eh_personality(), 1);
- let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() {
- Some(tydesc) => {
- let tydesc = bx.get_static(tydesc);
- bx.bitcast(tydesc, bx.type_i8p())
- }
- None => bx.const_null(bx.type_i8p()),
- };
+ let tydesc = bx.const_null(bx.type_i8p());
catch.add_clause(vals, tydesc);
let ptr = catch.extract_value(vals, 0);
catch.call(catch_func, &[data, ptr], None);
bx.store(ret, dest, i32_align);
}
+// Variant of codegen_gnu_try used for emscripten where Rust panics are
+// implemented using C++ exceptions. Here we use exceptions of a specific type
+// (`struct rust_panic`) to represent Rust panics.
+fn codegen_emcc_try(
+ bx: &mut Builder<'a, 'll, 'tcx>,
+ try_func: &'ll Value,
+ data: &'ll Value,
+ catch_func: &'ll Value,
+ dest: &'ll Value,
+) {
+ let llfn = get_rust_try_fn(bx, &mut |mut bx| {
+ // Codegens the shims described above:
+ //
+ // bx:
+ // invoke %try_func(%data) normal %normal unwind %catch
+ //
+ // normal:
+ // ret 0
+ //
+ // catch:
+ // (%ptr, %selector) = landingpad
+ // %rust_typeid = @llvm.eh.typeid.for(@_ZTI10rust_panic)
+ // %is_rust_panic = %selector == %rust_typeid
+ // %catch_data = alloca { i8*, i8 }
+ // %catch_data[0] = %ptr
+ // %catch_data[1] = %is_rust_panic
+ // call %catch_func(%data, %catch_data)
+ // ret 1
+
+ bx.sideeffect();
+
+ let mut then = bx.build_sibling_block("then");
+ let mut catch = bx.build_sibling_block("catch");
+
+ let try_func = llvm::get_param(bx.llfn(), 0);
+ let data = llvm::get_param(bx.llfn(), 1);
+ let catch_func = llvm::get_param(bx.llfn(), 2);
+ bx.invoke(try_func, &[data], then.llbb(), catch.llbb(), None);
+ then.ret(bx.const_i32(0));
+
+ // Type indicator for the exception being thrown.
+ //
+ // The first value in this tuple is a pointer to the exception object
+ // being thrown. The second value is a "selector" indicating which of
+ // the landing pad clauses the exception's type had been matched to.
+ let tydesc = bx.eh_catch_typeinfo();
+ let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false);
+ let vals = catch.landing_pad(lpad_ty, bx.eh_personality(), 2);
+ catch.add_clause(vals, tydesc);
+ catch.add_clause(vals, bx.const_null(bx.type_i8p()));
+ let ptr = catch.extract_value(vals, 0);
+ let selector = catch.extract_value(vals, 1);
+
+ // Check if the typeid we got is the one for a Rust panic.
+ let llvm_eh_typeid_for = bx.get_intrinsic("llvm.eh.typeid.for");
+ let rust_typeid = catch.call(llvm_eh_typeid_for, &[tydesc], None);
+ let is_rust_panic = catch.icmp(IntPredicate::IntEQ, selector, rust_typeid);
+ let is_rust_panic = catch.zext(is_rust_panic, bx.type_bool());
+
+ // We need to pass two values to catch_func (ptr and is_rust_panic), so
+ // create an alloca and pass a pointer to that.
+ let ptr_align = bx.tcx().data_layout.pointer_align.abi;
+ let i8_align = bx.tcx().data_layout.i8_align.abi;
+ let catch_data =
+ catch.alloca(bx.type_struct(&[bx.type_i8p(), bx.type_bool()], false), ptr_align);
+ let catch_data_0 = catch.inbounds_gep(catch_data, &[bx.const_usize(0), bx.const_usize(0)]);
+ catch.store(ptr, catch_data_0, ptr_align);
+ let catch_data_1 = catch.inbounds_gep(catch_data, &[bx.const_usize(0), bx.const_usize(1)]);
+ catch.store(is_rust_panic, catch_data_1, i8_align);
+ let catch_data = catch.bitcast(catch_data, bx.type_i8p());
+
+ catch.call(catch_func, &[data, catch_data], None);
+ catch.ret(bx.const_i32(1));
+ });
+
+ // Note that no invoke is used here because by definition this function
+ // can't panic (that's what it's catching).
+ let ret = bx.call(llfn, &[try_func, data, catch_func], None);
+ let i32_align = bx.tcx().data_layout.i32_align.abi;
+ bx.store(ret, dest, i32_align);
+}
+
// Helper function to give a Block to a closure to codegen a shim function.
// This is currently primarily used for the `try` intrinsic functions above.
fn gen_fn<'ll, 'tcx>(
use rustc_data_structures::sync::{par_iter, Lock, ParallelIterator};
use rustc_hir as hir;
use rustc_hir::def_id::{LocalDefId, LOCAL_CRATE};
-use rustc_hir::lang_items::StartFnLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_index::vec::Idx;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::middle::cstore::EncodedMetadata;
let (arg_argc, arg_argv) = get_argc_argv(cx, &mut bx);
let (start_fn, args) = if use_start_lang_item {
- let start_def_id = cx.tcx().require_lang_item(StartFnLangItem, None);
+ let start_def_id = cx.tcx().require_lang_item(LangItem::Start, None);
let start_fn = cx.get_fn_addr(
ty::Instance::resolve(
cx.tcx(),
use crate::MemFlags;
use rustc_ast as ast;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_index::vec::Idx;
use rustc_middle::mir;
use rustc_middle::mir::interpret::{AllocId, ConstValue, Pointer, Scalar};
let index = self.codegen_operand(&mut bx, index).immediate();
// It's `fn panic_bounds_check(index: usize, len: usize)`,
// and `#[track_caller]` adds an implicit third argument.
- (lang_items::PanicBoundsCheckFnLangItem, vec![index, len, location])
+ (LangItem::PanicBoundsCheck, vec![index, len, location])
}
_ => {
let msg_str = Symbol::intern(msg.description());
let msg = bx.const_str(msg_str);
// It's `pub fn panic(expr: &str)`, with the wide reference being passed
// as two arguments, and `#[track_caller]` adds an implicit third argument.
- (lang_items::PanicFnLangItem, vec![msg.0, msg.1, location])
+ (LangItem::Panic, vec![msg.0, msg.1, location])
}
};
// Obtain the panic entry point.
// FIXME: dedup this with `codegen_assert_terminator` above.
- let def_id =
- common::langcall(bx.tcx(), Some(span), "", lang_items::PanicFnLangItem);
+ let def_id = common::langcall(bx.tcx(), Some(span), "", LangItem::Panic);
let instance = ty::Instance::mono(bx.tcx(), def_id);
let fn_abi = FnAbi::of_instance(bx, instance, &[]);
let llfn = bx.get_fn_addr(instance);
use crate::MemFlags;
use rustc_apfloat::{ieee, Float, Round, Status};
-use rustc_hir::lang_items::ExchangeMallocFnLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::mir;
use rustc_middle::ty::cast::{CastTy, IntTy};
use rustc_middle::ty::layout::{HasTyCtxt, TyAndLayout};
let llty_ptr = bx.cx().backend_type(box_layout);
// Allocate space:
- let def_id = match bx.tcx().lang_items().require(ExchangeMallocFnLangItem) {
+ let def_id = match bx.tcx().lang_items().require(LangItem::ExchangeMalloc) {
Ok(id) => id,
Err(s) => {
bx.cx().sess().fatal(&format!("allocation of `{}` {}", box_layout.ty, s));
Erroneous code example:
-```rust
+```ignore (multiple source files required for compile_fail)
// file: ambiguous_module/mod.rs
fn foo() {}
-```
-```rust
// file: ambiguous_module.rs
fn foo() {}
-```
-```ignore (multiple source files required for compile_fail)
+// file: lib.rs
+
mod ambiguous_module; // error: file for module `ambiguous_module`
// found at both ambiguous_module.rs and
// ambiguous_module.rs/mod.rs
-
-fn main() {}
```
Please remove this ambiguity by deleting/renaming one of the candidate files.
}
}
- pub fn matches_ns(&self, ns: Namespace) -> bool {
+ /// Returns `None` if this is `Res::Err`
+ pub fn ns(&self) -> Option<Namespace> {
match self {
- Res::Def(kind, ..) => kind.ns() == Some(ns),
- Res::PrimTy(..) | Res::SelfTy(..) | Res::ToolMod => ns == Namespace::TypeNS,
- Res::SelfCtor(..) | Res::Local(..) => ns == Namespace::ValueNS,
- Res::NonMacroAttr(..) => ns == Namespace::MacroNS,
- Res::Err => true,
+ Res::Def(kind, ..) => kind.ns(),
+ Res::PrimTy(..) | Res::SelfTy(..) | Res::ToolMod => Some(Namespace::TypeNS),
+ Res::SelfCtor(..) | Res::Local(..) => Some(Namespace::ValueNS),
+ Res::NonMacroAttr(..) => Some(Namespace::MacroNS),
+ Res::Err => None,
}
}
+
+ /// Always returns `true` if `self` is `Res::Err`
+ pub fn matches_ns(&self, ns: Namespace) -> bool {
+ self.ns().map_or(true, |actual_ns| actual_ns == ns)
+ }
}
//! * Traits that represent operators; e.g., `Add`, `Sub`, `Index`.
//! * Functions called by the compiler itself.
-pub use self::LangItem::*;
-
use crate::def_id::DefId;
use crate::{MethodKind, Target};
/// that is `#[lang = "eq"]` would result in `sym::eq`.
pub fn name(self) -> Symbol {
match self {
- $( $variant => $name, )*
+ $( LangItem::$variant => $name, )*
}
}
pub fn group(self) -> Option<LangItemGroup> {
use LangItemGroup::*;
match self {
- $( $variant => expand_group!($($group)*), )*
+ $( LangItem::$variant => expand_group!($($group)*), )*
}
}
}
fn init_none(_: LangItem) -> Option<DefId> { None }
Self {
- items: vec![$(init_none($variant)),*],
+ items: vec![$(init_none(LangItem::$variant)),*],
missing: Vec::new(),
groups: [vec![]; NUM_GROUPS],
}
/// exists.
#[allow(dead_code)]
pub fn $method(&self) -> Option<DefId> {
- self.items[$variant as usize]
+ self.items[LangItem::$variant as usize]
}
)*
}
/// A mapping from the name of the lang item to its order and the form it must be of.
pub static ref ITEM_REFS: FxHashMap<Symbol, (usize, Target)> = {
let mut item_refs = FxHashMap::default();
- $( item_refs.insert($name, ($variant as usize, $target)); )*
+ $( item_refs.insert($name, (LangItem::$variant as usize, $target)); )*
item_refs
};
}
}
language_item_table! {
-// Variant name, Name, Method name, Target;
- BoolImplItem, sym::bool, bool_impl, Target::Impl;
- CharImplItem, sym::char, char_impl, Target::Impl;
- StrImplItem, sym::str, str_impl, Target::Impl;
- ArrayImplItem, sym::array, array_impl, Target::Impl;
- SliceImplItem, sym::slice, slice_impl, Target::Impl;
- SliceU8ImplItem, sym::slice_u8, slice_u8_impl, Target::Impl;
- StrAllocImplItem, sym::str_alloc, str_alloc_impl, Target::Impl;
- SliceAllocImplItem, sym::slice_alloc, slice_alloc_impl, Target::Impl;
- SliceU8AllocImplItem, sym::slice_u8_alloc, slice_u8_alloc_impl, Target::Impl;
- ConstPtrImplItem, sym::const_ptr, const_ptr_impl, Target::Impl;
- MutPtrImplItem, sym::mut_ptr, mut_ptr_impl, Target::Impl;
- ConstSlicePtrImplItem, sym::const_slice_ptr, const_slice_ptr_impl, Target::Impl;
- MutSlicePtrImplItem, sym::mut_slice_ptr, mut_slice_ptr_impl, Target::Impl;
- I8ImplItem, sym::i8, i8_impl, Target::Impl;
- I16ImplItem, sym::i16, i16_impl, Target::Impl;
- I32ImplItem, sym::i32, i32_impl, Target::Impl;
- I64ImplItem, sym::i64, i64_impl, Target::Impl;
- I128ImplItem, sym::i128, i128_impl, Target::Impl;
- IsizeImplItem, sym::isize, isize_impl, Target::Impl;
- U8ImplItem, sym::u8, u8_impl, Target::Impl;
- U16ImplItem, sym::u16, u16_impl, Target::Impl;
- U32ImplItem, sym::u32, u32_impl, Target::Impl;
- U64ImplItem, sym::u64, u64_impl, Target::Impl;
- U128ImplItem, sym::u128, u128_impl, Target::Impl;
- UsizeImplItem, sym::usize, usize_impl, Target::Impl;
- F32ImplItem, sym::f32, f32_impl, Target::Impl;
- F64ImplItem, sym::f64, f64_impl, Target::Impl;
- F32RuntimeImplItem, sym::f32_runtime, f32_runtime_impl, Target::Impl;
- F64RuntimeImplItem, sym::f64_runtime, f64_runtime_impl, Target::Impl;
-
- SizedTraitLangItem, sym::sized, sized_trait, Target::Trait;
- UnsizeTraitLangItem, sym::unsize, unsize_trait, Target::Trait;
- // trait injected by #[derive(PartialEq)], (i.e. "Partial EQ").
- StructuralPeqTraitLangItem, sym::structural_peq, structural_peq_trait, Target::Trait;
- // trait injected by #[derive(Eq)], (i.e. "Total EQ"; no, I will not apologize).
- StructuralTeqTraitLangItem, sym::structural_teq, structural_teq_trait, Target::Trait;
- CopyTraitLangItem, sym::copy, copy_trait, Target::Trait;
- CloneTraitLangItem, sym::clone, clone_trait, Target::Trait;
- SyncTraitLangItem, sym::sync, sync_trait, Target::Trait;
- DiscriminantKindTraitLangItem, sym::discriminant_kind, discriminant_kind_trait, Target::Trait;
+// Variant name, Name, Method name, Target;
+ Bool, sym::bool, bool_impl, Target::Impl;
+ Char, sym::char, char_impl, Target::Impl;
+ Str, sym::str, str_impl, Target::Impl;
+ Array, sym::array, array_impl, Target::Impl;
+ Slice, sym::slice, slice_impl, Target::Impl;
+ SliceU8, sym::slice_u8, slice_u8_impl, Target::Impl;
+ StrAlloc, sym::str_alloc, str_alloc_impl, Target::Impl;
+ SliceAlloc, sym::slice_alloc, slice_alloc_impl, Target::Impl;
+ SliceU8Alloc, sym::slice_u8_alloc, slice_u8_alloc_impl, Target::Impl;
+ ConstPtr, sym::const_ptr, const_ptr_impl, Target::Impl;
+ MutPtr, sym::mut_ptr, mut_ptr_impl, Target::Impl;
+ ConstSlicePtr, sym::const_slice_ptr, const_slice_ptr_impl, Target::Impl;
+ MutSlicePtr, sym::mut_slice_ptr, mut_slice_ptr_impl, Target::Impl;
+ I8, sym::i8, i8_impl, Target::Impl;
+ I16, sym::i16, i16_impl, Target::Impl;
+ I32, sym::i32, i32_impl, Target::Impl;
+ I64, sym::i64, i64_impl, Target::Impl;
+ I128, sym::i128, i128_impl, Target::Impl;
+ Isize, sym::isize, isize_impl, Target::Impl;
+ U8, sym::u8, u8_impl, Target::Impl;
+ U16, sym::u16, u16_impl, Target::Impl;
+ U32, sym::u32, u32_impl, Target::Impl;
+ U64, sym::u64, u64_impl, Target::Impl;
+ U128, sym::u128, u128_impl, Target::Impl;
+ Usize, sym::usize, usize_impl, Target::Impl;
+ F32, sym::f32, f32_impl, Target::Impl;
+ F64, sym::f64, f64_impl, Target::Impl;
+ F32Runtime, sym::f32_runtime, f32_runtime_impl, Target::Impl;
+ F64Runtime, sym::f64_runtime, f64_runtime_impl, Target::Impl;
+
+ Sized, sym::sized, sized_trait, Target::Trait;
+ Unsize, sym::unsize, unsize_trait, Target::Trait;
+ // Trait injected by #[derive(PartialEq)], (i.e. "Partial EQ").
+ StructuralPeq, sym::structural_peq, structural_peq_trait, Target::Trait;
+ // Trait injected by #[derive(Eq)], (i.e. "Total EQ"; no, I will not apologize).
+ StructuralTeq, sym::structural_teq, structural_teq_trait, Target::Trait;
+ Copy, sym::copy, copy_trait, Target::Trait;
+ Clone, sym::clone, clone_trait, Target::Trait;
+ Sync, sym::sync, sync_trait, Target::Trait;
+ DiscriminantKind, sym::discriminant_kind, discriminant_kind_trait, Target::Trait;
// The associated item of `trait DiscriminantKind`.
- DiscriminantTypeLangItem, sym::discriminant_type, discriminant_type, Target::AssocTy;
-
- FreezeTraitLangItem, sym::freeze, freeze_trait, Target::Trait;
-
- DropTraitLangItem, sym::drop, drop_trait, Target::Trait;
-
- CoerceUnsizedTraitLangItem, sym::coerce_unsized, coerce_unsized_trait, Target::Trait;
- DispatchFromDynTraitLangItem, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait;
-
- AddTraitLangItem(Op), sym::add, add_trait, Target::Trait;
- SubTraitLangItem(Op), sym::sub, sub_trait, Target::Trait;
- MulTraitLangItem(Op), sym::mul, mul_trait, Target::Trait;
- DivTraitLangItem(Op), sym::div, div_trait, Target::Trait;
- RemTraitLangItem(Op), sym::rem, rem_trait, Target::Trait;
- NegTraitLangItem(Op), sym::neg, neg_trait, Target::Trait;
- NotTraitLangItem(Op), sym::not, not_trait, Target::Trait;
- BitXorTraitLangItem(Op), sym::bitxor, bitxor_trait, Target::Trait;
- BitAndTraitLangItem(Op), sym::bitand, bitand_trait, Target::Trait;
- BitOrTraitLangItem(Op), sym::bitor, bitor_trait, Target::Trait;
- ShlTraitLangItem(Op), sym::shl, shl_trait, Target::Trait;
- ShrTraitLangItem(Op), sym::shr, shr_trait, Target::Trait;
- AddAssignTraitLangItem(Op), sym::add_assign, add_assign_trait, Target::Trait;
- SubAssignTraitLangItem(Op), sym::sub_assign, sub_assign_trait, Target::Trait;
- MulAssignTraitLangItem(Op), sym::mul_assign, mul_assign_trait, Target::Trait;
- DivAssignTraitLangItem(Op), sym::div_assign, div_assign_trait, Target::Trait;
- RemAssignTraitLangItem(Op), sym::rem_assign, rem_assign_trait, Target::Trait;
- BitXorAssignTraitLangItem(Op), sym::bitxor_assign, bitxor_assign_trait, Target::Trait;
- BitAndAssignTraitLangItem(Op), sym::bitand_assign, bitand_assign_trait, Target::Trait;
- BitOrAssignTraitLangItem(Op), sym::bitor_assign, bitor_assign_trait, Target::Trait;
- ShlAssignTraitLangItem(Op), sym::shl_assign, shl_assign_trait, Target::Trait;
- ShrAssignTraitLangItem(Op), sym::shr_assign, shr_assign_trait, Target::Trait;
- IndexTraitLangItem(Op), sym::index, index_trait, Target::Trait;
- IndexMutTraitLangItem(Op), sym::index_mut, index_mut_trait, Target::Trait;
-
- UnsafeCellTypeLangItem, sym::unsafe_cell, unsafe_cell_type, Target::Struct;
- VaListTypeLangItem, sym::va_list, va_list, Target::Struct;
-
- DerefTraitLangItem, sym::deref, deref_trait, Target::Trait;
- DerefMutTraitLangItem, sym::deref_mut, deref_mut_trait, Target::Trait;
- ReceiverTraitLangItem, sym::receiver, receiver_trait, Target::Trait;
-
- FnTraitLangItem, kw::Fn, fn_trait, Target::Trait;
- FnMutTraitLangItem, sym::fn_mut, fn_mut_trait, Target::Trait;
- FnOnceTraitLangItem, sym::fn_once, fn_once_trait, Target::Trait;
-
- FnOnceOutputLangItem, sym::fn_once_output, fn_once_output, Target::AssocTy;
-
- FutureTraitLangItem, sym::future_trait, future_trait, Target::Trait;
- GeneratorStateLangItem, sym::generator_state, gen_state, Target::Enum;
- GeneratorTraitLangItem, sym::generator, gen_trait, Target::Trait;
- UnpinTraitLangItem, sym::unpin, unpin_trait, Target::Trait;
- PinTypeLangItem, sym::pin, pin_type, Target::Struct;
-
- // Don't be fooled by the naming here: this lang item denotes `PartialEq`, not `Eq`.
- EqTraitLangItem, sym::eq, eq_trait, Target::Trait;
- PartialOrdTraitLangItem, sym::partial_ord, partial_ord_trait, Target::Trait;
-
- // A number of panic-related lang items. The `panic` item corresponds to
- // divide-by-zero and various panic cases with `match`. The
- // `panic_bounds_check` item is for indexing arrays.
+ Discriminant, sym::discriminant_type, discriminant_type, Target::AssocTy;
+
+ Freeze, sym::freeze, freeze_trait, Target::Trait;
+
+ Drop, sym::drop, drop_trait, Target::Trait;
+
+ CoerceUnsized, sym::coerce_unsized, coerce_unsized_trait, Target::Trait;
+ DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait;
+
+ Add(Op), sym::add, add_trait, Target::Trait;
+ Sub(Op), sym::sub, sub_trait, Target::Trait;
+ Mul(Op), sym::mul, mul_trait, Target::Trait;
+ Div(Op), sym::div, div_trait, Target::Trait;
+ Rem(Op), sym::rem, rem_trait, Target::Trait;
+ Neg(Op), sym::neg, neg_trait, Target::Trait;
+ Not(Op), sym::not, not_trait, Target::Trait;
+ BitXor(Op), sym::bitxor, bitxor_trait, Target::Trait;
+ BitAnd(Op), sym::bitand, bitand_trait, Target::Trait;
+ BitOr(Op), sym::bitor, bitor_trait, Target::Trait;
+ Shl(Op), sym::shl, shl_trait, Target::Trait;
+ Shr(Op), sym::shr, shr_trait, Target::Trait;
+ AddAssign(Op), sym::add_assign, add_assign_trait, Target::Trait;
+ SubAssign(Op), sym::sub_assign, sub_assign_trait, Target::Trait;
+ MulAssign(Op), sym::mul_assign, mul_assign_trait, Target::Trait;
+ DivAssign(Op), sym::div_assign, div_assign_trait, Target::Trait;
+ RemAssign(Op), sym::rem_assign, rem_assign_trait, Target::Trait;
+ BitXorAssign(Op), sym::bitxor_assign, bitxor_assign_trait, Target::Trait;
+ BitAndAssign(Op), sym::bitand_assign, bitand_assign_trait, Target::Trait;
+ BitOrAssign(Op), sym::bitor_assign, bitor_assign_trait, Target::Trait;
+ ShlAssign(Op), sym::shl_assign, shl_assign_trait, Target::Trait;
+ ShrAssign(Op), sym::shr_assign, shr_assign_trait, Target::Trait;
+ Index(Op), sym::index, index_trait, Target::Trait;
+ IndexMut(Op), sym::index_mut, index_mut_trait, Target::Trait;
+
+ UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct;
+ VaList, sym::va_list, va_list, Target::Struct;
+
+ Deref, sym::deref, deref_trait, Target::Trait;
+ DerefMut, sym::deref_mut, deref_mut_trait, Target::Trait;
+ Receiver, sym::receiver, receiver_trait, Target::Trait;
+
+ Fn, kw::Fn, fn_trait, Target::Trait;
+ FnMut, sym::fn_mut, fn_mut_trait, Target::Trait;
+ FnOnce, sym::fn_once, fn_once_trait, Target::Trait;
+
+ FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy;
+
+ Future, sym::future_trait, future_trait, Target::Trait;
+ GeneratorState, sym::generator_state, gen_state, Target::Enum;
+ Generator, sym::generator, gen_trait, Target::Trait;
+ Unpin, sym::unpin, unpin_trait, Target::Trait;
+ Pin, sym::pin, pin_type, Target::Struct;
+
+ PartialEq, sym::eq, eq_trait, Target::Trait;
+ PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait;
+
+ // A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and
+ // various panic cases with `match`. The `panic_bounds_check` item is for indexing arrays.
//
- // The `begin_unwind` lang item has a predefined symbol name and is sort of
- // a "weak lang item" in the sense that a crate is not required to have it
- // defined to use it, but a final product is required to define it
- // somewhere. Additionally, there are restrictions on crates that use a weak
- // lang item, but do not have it defined.
- PanicFnLangItem, sym::panic, panic_fn, Target::Fn;
- PanicBoundsCheckFnLangItem, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn;
- PanicInfoLangItem, sym::panic_info, panic_info, Target::Struct;
- PanicLocationLangItem, sym::panic_location, panic_location, Target::Struct;
- PanicImplLangItem, sym::panic_impl, panic_impl, Target::Fn;
- // Libstd panic entry point. Necessary for const eval to be able to catch it
- BeginPanicFnLangItem, sym::begin_panic, begin_panic_fn, Target::Fn;
+ // The `begin_unwind` lang item has a predefined symbol name and is sort of a "weak lang item"
+ // in the sense that a crate is not required to have it defined to use it, but a final product
+ // is required to define it somewhere. Additionally, there are restrictions on crates that use
+ // a weak lang item, but do not have it defined.
+ Panic, sym::panic, panic_fn, Target::Fn;
+ PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn;
+ PanicInfo, sym::panic_info, panic_info, Target::Struct;
+ PanicLocation, sym::panic_location, panic_location, Target::Struct;
+ PanicImpl, sym::panic_impl, panic_impl, Target::Fn;
+ // libstd panic entry point. Necessary for const eval to be able to catch it
+ BeginPanic, sym::begin_panic, begin_panic_fn, Target::Fn;
- ExchangeMallocFnLangItem, sym::exchange_malloc, exchange_malloc_fn, Target::Fn;
- BoxFreeFnLangItem, sym::box_free, box_free_fn, Target::Fn;
- DropInPlaceFnLangItem, sym::drop_in_place, drop_in_place_fn, Target::Fn;
- OomLangItem, sym::oom, oom, Target::Fn;
- AllocLayoutLangItem, sym::alloc_layout, alloc_layout, Target::Struct;
+ ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn;
+ BoxFree, sym::box_free, box_free_fn, Target::Fn;
+ DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn;
+ Oom, sym::oom, oom, Target::Fn;
+ AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct;
- StartFnLangItem, sym::start, start_fn, Target::Fn;
+ Start, sym::start, start_fn, Target::Fn;
- EhPersonalityLangItem, sym::eh_personality, eh_personality, Target::Fn;
- EhCatchTypeinfoLangItem, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static;
+ EhPersonality, sym::eh_personality, eh_personality, Target::Fn;
+ EhCatchTypeinfo, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static;
- OwnedBoxLangItem, sym::owned_box, owned_box, Target::Struct;
+ OwnedBox, sym::owned_box, owned_box, Target::Struct;
- PhantomDataItem, sym::phantom_data, phantom_data, Target::Struct;
+ PhantomData, sym::phantom_data, phantom_data, Target::Struct;
- ManuallyDropItem, sym::manually_drop, manually_drop, Target::Struct;
+ ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct;
- MaybeUninitLangItem, sym::maybe_uninit, maybe_uninit, Target::Union;
+ MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union;
// Align offset for stride != 1; must not panic.
- AlignOffsetLangItem, sym::align_offset, align_offset_fn, Target::Fn;
+ AlignOffset, sym::align_offset, align_offset_fn, Target::Fn;
- TerminationTraitLangItem, sym::termination, termination, Target::Trait;
+ Termination, sym::termination, termination, Target::Trait;
- TryTraitLangItem, kw::Try, try_trait, Target::Trait;
+ Try, kw::Try, try_trait, Target::Trait;
// Language items from AST lowering
- TryFromError, sym::from_error, from_error_fn, Target::Method(MethodKind::Trait { body: false });
- TryFromOk, sym::from_ok, from_ok_fn, Target::Method(MethodKind::Trait { body: false });
- TryIntoResult, sym::into_result, into_result_fn, Target::Method(MethodKind::Trait { body: false });
+ TryFromError, sym::from_error, from_error_fn, Target::Method(MethodKind::Trait { body: false });
+ TryFromOk, sym::from_ok, from_ok_fn, Target::Method(MethodKind::Trait { body: false });
+ TryIntoResult, sym::into_result, into_result_fn, Target::Method(MethodKind::Trait { body: false });
- PollReady, sym::Ready, poll_ready_variant, Target::Variant;
- PollPending, sym::Pending, poll_pending_variant, Target::Variant;
+ PollReady, sym::Ready, poll_ready_variant, Target::Variant;
+ PollPending, sym::Pending, poll_pending_variant, Target::Variant;
- FromGenerator, sym::from_generator, from_generator_fn, Target::Fn;
- GetContext, sym::get_context, get_context_fn, Target::Fn;
+ FromGenerator, sym::from_generator, from_generator_fn, Target::Fn;
+ GetContext, sym::get_context, get_context_fn, Target::Fn;
- FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false });
+ FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false });
- FromFrom, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false });
+ FromFrom, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false });
- OptionSome, sym::Some, option_some_variant, Target::Variant;
- OptionNone, sym::None, option_none_variant, Target::Variant;
+ OptionSome, sym::Some, option_some_variant, Target::Variant;
+ OptionNone, sym::None, option_none_variant, Target::Variant;
- ResultOk, sym::Ok, result_ok_variant, Target::Variant;
- ResultErr, sym::Err, result_err_variant, Target::Variant;
+ ResultOk, sym::Ok, result_ok_variant, Target::Variant;
+ ResultErr, sym::Err, result_err_variant, Target::Variant;
- IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false });
- IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false});
+ IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false });
+ IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false});
- PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent);
+ PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent);
- RangeFrom, sym::RangeFrom, range_from_struct, Target::Struct;
- RangeFull, sym::RangeFull, range_full_struct, Target::Struct;
- RangeInclusiveStruct, sym::RangeInclusive, range_inclusive_struct, Target::Struct;
- RangeInclusiveNew, sym::range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent);
- Range, sym::Range, range_struct, Target::Struct;
- RangeToInclusive, sym::RangeToInclusive, range_to_inclusive_struct, Target::Struct;
- RangeTo, sym::RangeTo, range_to_struct, Target::Struct;
+ RangeFrom, sym::RangeFrom, range_from_struct, Target::Struct;
+ RangeFull, sym::RangeFull, range_full_struct, Target::Struct;
+ RangeInclusiveStruct, sym::RangeInclusive, range_inclusive_struct, Target::Struct;
+ RangeInclusiveNew, sym::range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent);
+ Range, sym::Range, range_struct, Target::Struct;
+ RangeToInclusive, sym::RangeToInclusive, range_to_inclusive_struct, Target::Struct;
+ RangeTo, sym::RangeTo, range_to_struct, Target::Struct;
}
lazy_static! {
pub static ref WEAK_ITEMS_REFS: FxHashMap<Symbol, LangItem> = {
let mut map = FxHashMap::default();
- $(map.insert(sym::$name, lang_items::$item);)*
+ $(map.insert(sym::$name, LangItem::$item);)*
map
};
}
) }
weak_lang_items! {
- panic_impl, PanicImplLangItem, rust_begin_unwind;
- eh_personality, EhPersonalityLangItem, rust_eh_personality;
- oom, OomLangItem, rust_oom;
+ panic_impl, PanicImpl, rust_begin_unwind;
+ eh_personality, EhPersonality, rust_eh_personality;
+ eh_catch_typeinfo, EhCatchTypeinfo, rust_eh_catch_typeinfo;
+ oom, Oom, rust_oom;
}
use super::{InferCtxt, RegionVariableOrigin, SubregionOrigin, TypeTrace, ValuePairs};
use crate::infer;
+use crate::infer::OriginalQueryValues;
use crate::traits::error_reporting::report_object_safety_error;
use crate::traits::{
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{Item, ItemKind, Node};
use rustc_middle::ty::error::TypeError;
+use rustc_middle::ty::ParamEnvAnd;
use rustc_middle::ty::{
self,
subst::{Subst, SubstsRef},
};
if let Some(exp_found) = exp_found {
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
+ self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
}
// In some (most?) cases cause.body_id points to actual body, but in some cases
self.note_error_origin(diag, cause, exp_found);
}
+ fn suggest_await_on_expect_found(
+ &self,
+ cause: &ObligationCause<'tcx>,
+ exp_span: Span,
+ exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
+ diag: &mut DiagnosticBuilder<'tcx>,
+ ) {
+ debug!(
+ "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
+ exp_span, exp_found.expected, exp_found.found
+ );
+
+ if let ty::Opaque(def_id, _) = exp_found.expected.kind {
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
+ // Future::Output
+ let item_def_id = self
+ .tcx
+ .associated_items(future_trait)
+ .in_definition_order()
+ .next()
+ .unwrap()
+ .def_id;
+
+ let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
+ if let Some(projection_ty) = projection_ty {
+ let projection_query = self.canonicalize_query(
+ &ParamEnvAnd { param_env: self.tcx.param_env(def_id), value: projection_ty },
+ &mut OriginalQueryValues::default(),
+ );
+ if let Ok(resp) = self.tcx.normalize_projection_ty(projection_query) {
+ let normalized_ty = resp.value.value.normalized_ty;
+ debug!("suggest_await_on_expect_found: normalized={:?}", normalized_ty);
+ if ty::TyS::same_type(normalized_ty, exp_found.found) {
+ let span = if let ObligationCauseCode::Pattern {
+ span,
+ origin_expr: _,
+ root_ty: _,
+ } = cause.code
+ {
+ // scrutinee's span
+ span.unwrap_or(exp_span)
+ } else {
+ exp_span
+ };
+ diag.span_suggestion_verbose(
+ span.shrink_to_hi(),
+ "consider awaiting on the future",
+ ".await".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ }
+ }
+
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
/// suggests it.
fn suggest_as_ref_where_appropriate(
#![feature(extend_one)]
#![feature(never_type)]
#![feature(or_patterns)]
-#![feature(range_is_empty)]
#![feature(in_band_lifetimes)]
#![feature(crate_visibility_modifier)]
#![recursion_limit = "512"] // For rustdoc
# Note that this crate purposefully does not depend on other rustc crates
[dependencies]
unicode-xid = "0.2.0"
+
+[dev-dependencies]
+expect-test = "0.1"
/// Parsed token.
/// It doesn't contain information about data that has been parsed,
/// only the type of the token and its size.
+#[derive(Debug)]
pub struct Token {
pub kind: TokenKind,
pub len: usize,
use super::*;
+use expect_test::{expect, Expect};
+
fn check_raw_str(s: &str, expected_hashes: u16, expected_err: Option<RawStrError>) {
let s = &format!("r{}", s);
let mut cursor = Cursor::new(s);
let input = "#!/bin/rust-scripts\n#![allow_unused(true)]";
assert_eq!(strip_shebang(input), Some(19));
}
+
+fn check_lexing(src: &str, expect: Expect) {
+ let actual: String = tokenize(src).map(|token| format!("{:?}\n", token)).collect();
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn comment_flavors() {
+ check_lexing(
+ r"
+// line
+//// line as well
+/// outer doc line
+//! inner doc line
+/* block */
+/**/
+/*** also block */
+/** outer doc block */
+/*! inner doc block */
+",
+ expect![[r#"
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: LineComment { doc_style: None }, len: 7 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: LineComment { doc_style: None }, len: 17 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: LineComment { doc_style: Some(Outer) }, len: 18 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: LineComment { doc_style: Some(Inner) }, len: 18 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: BlockComment { doc_style: None, terminated: true }, len: 11 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: BlockComment { doc_style: None, terminated: true }, len: 4 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: BlockComment { doc_style: None, terminated: true }, len: 18 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: BlockComment { doc_style: Some(Outer), terminated: true }, len: 22 }
+ Token { kind: Whitespace, len: 1 }
+ Token { kind: BlockComment { doc_style: Some(Inner), terminated: true }, len: 22 }
+ Token { kind: Whitespace, len: 1 }
+ "#]],
+ )
+}
use rustc_hir::def_id::DefId;
use rustc_hir::{ForeignItemKind, GenericParamKind, PatKind};
use rustc_hir::{HirId, HirIdSet, Node};
+use rustc_index::vec::Idx;
use rustc_middle::lint::LintDiagnosticBuilder;
use rustc_middle::ty::subst::{GenericArgKind, Subst};
-use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::ty::{self, layout::LayoutError, Ty, TyCtxt};
use rustc_session::lint::FutureIncompatibleInfo;
use rustc_session::Session;
use rustc_span::edition::Edition;
ckind: CItemKind,
) -> bool {
debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b);
+ let tcx = cx.tcx;
+
+ // Given a transparent newtype, reach through and grab the inner
+ // type unless the newtype makes the type non-null.
+ let non_transparent_ty = |ty: Ty<'tcx>| -> Ty<'tcx> {
+ let mut ty = ty;
+ loop {
+ if let ty::Adt(def, substs) = ty.kind {
+ let is_transparent = def.subst(tcx, substs).repr.transparent();
+ let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, &def);
+ debug!(
+ "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
+ ty, is_transparent, is_non_null
+ );
+ if is_transparent && !is_non_null {
+ debug_assert!(def.variants.len() == 1);
+ let v = &def.variants[VariantIdx::new(0)];
+ ty = v
+ .transparent_newtype_field(tcx)
+ .expect(
+ "single-variant transparent structure with zero-sized field",
+ )
+ .ty(tcx, substs);
+ continue;
+ }
+ }
+ debug!("non_transparent_ty -> {:?}", ty);
+ return ty;
+ }
+ };
+
+ let a = non_transparent_ty(a);
+ let b = non_transparent_ty(b);
+
if !seen_types.insert((a, b)) {
// We've encountered a cycle. There's no point going any further -- the types are
// structurally the same.
let a_kind = &a.kind;
let b_kind = &b.kind;
- let compare_layouts = |a, b| -> bool {
- let a_layout = &cx.layout_of(a).unwrap().layout.abi;
- let b_layout = &cx.layout_of(b).unwrap().layout.abi;
- debug!("{:?} == {:?} = {}", a_layout, b_layout, a_layout == b_layout);
- a_layout == b_layout
+ let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> {
+ debug!("compare_layouts({:?}, {:?})", a, b);
+ let a_layout = &cx.layout_of(a)?.layout.abi;
+ let b_layout = &cx.layout_of(b)?.layout.abi;
+ debug!(
+ "comparing layouts: {:?} == {:?} = {}",
+ a_layout,
+ b_layout,
+ a_layout == b_layout
+ );
+ Ok(a_layout == b_layout)
};
#[allow(rustc::usage_of_ty_tykind)]
let b = b.subst(cx.tcx, b_substs);
debug!("Comparing {:?} and {:?}", a, b);
+ // We can immediately rule out these types as structurally same if
+ // their layouts differ.
+ match compare_layouts(a, b) {
+ Ok(false) => return false,
+ _ => (), // otherwise, continue onto the full, fields comparison
+ }
+
// Grab a flattened representation of all fields.
let a_fields = a_def.variants.iter().flat_map(|v| v.fields.iter());
let b_fields = b_def.variants.iter().flat_map(|v| v.fields.iter());
- compare_layouts(a, b)
- && a_fields.eq_by(
+
+ // Perform a structural comparison for each field.
+ a_fields.eq_by(
b_fields,
|&ty::FieldDef { did: a_did, .. },
&ty::FieldDef { did: b_did, .. }| {
if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) {
ty == primitive
} else {
- compare_layouts(a, b)
+ compare_layouts(a, b).unwrap_or(false)
}
}
// Otherwise, just compare the layouts. This may fail to lint for some
// incompatible types, but at the very least, will stop reads into
// uninitialised memory.
- _ => compare_layouts(a, b),
+ _ => compare_layouts(a, b).unwrap_or(false),
}
})
}
use rustc_middle::mir::interpret::{sign_extend, truncate};
use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton};
use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::{self, AdtKind, Ty, TypeFoldable};
+use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable};
use rustc_span::source_map;
use rustc_span::symbol::sym;
use rustc_span::{Span, DUMMY_SP};
FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
}
+crate fn nonnull_optimization_guaranteed<'tcx>(tcx: TyCtxt<'tcx>, def: &ty::AdtDef) -> bool {
+ tcx.get_attrs(def.did)
+ .iter()
+ .any(|a| tcx.sess.check_name(a, sym::rustc_nonnull_optimization_guaranteed))
+}
+
/// Is type known to be non-null?
-fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
+crate fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
let tcx = cx.tcx;
match ty.kind {
ty::FnPtr(_) => true,
ty::Ref(..) => true,
ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => {
- let guaranteed_nonnull_optimization = tcx
- .get_attrs(def.did)
- .iter()
- .any(|a| tcx.sess.check_name(a, sym::rustc_nonnull_optimization_guaranteed));
+ let marked_non_null = nonnull_optimization_guaranteed(tcx, &def);
- if guaranteed_nonnull_optimization {
+ if marked_non_null {
return true;
}
+
for variant in &def.variants {
if let Some(field) = variant.transparent_newtype_field(tcx) {
if ty_is_known_nonnull(cx, field.ty(tcx, substs), mode) {
continue;
}
+ // Include path contains host directory, replace it with target
+ if is_crossed && flag.starts_with("-I") {
+ cfg.flag(&flag.replace(&host, &target));
+ continue;
+ }
+
cfg.flag(flag);
}
if !is_crossed {
cmd.arg("--system-libs");
+ } else if target.contains("windows-gnu") {
+ println!("cargo:rustc-link-lib=shell32");
+ println!("cargo:rustc-link-lib=uuid");
+ } else if target.contains("netbsd") {
+ println!("cargo:rustc-link-lib=z");
}
cmd.args(&components);
doctest = false
[dependencies]
-flate2 = "1.0"
libc = "0.2"
+snap = "1"
tracing = "0.1"
memmap = "0.7"
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
#[cfg(unix)]
mod dl {
- use std::ffi::{CStr, CString, OsStr};
+ use std::ffi::{CString, OsStr};
use std::os::unix::prelude::*;
- use std::ptr;
- use std::str;
- pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
- check_for_errors_in(|| unsafe {
- let s = CString::new(filename.as_bytes()).unwrap();
- libc::dlopen(s.as_ptr(), libc::RTLD_LAZY) as *mut u8
- })
- }
+ // As of the 2017 revision of the POSIX standard (IEEE 1003.1-2017), it is
+ // implementation-defined whether `dlerror` is thread-safe (in which case it returns the most
+ // recent error in the calling thread) or not thread-safe (in which case it returns the most
+ // recent error in *any* thread).
+ //
+ // There's no easy way to tell what strategy is used by a given POSIX implementation, so we
+ // lock around all calls that can modify `dlerror` in this module lest we accidentally read an
+ // error from a different thread. This is bulletproof when we are the *only* code using the
+ // dynamic library APIs at a given point in time. However, it's still possible for us to race
+ // with other code (see #74469) on platforms where `dlerror` is not thread-safe.
+ mod error {
+ use std::ffi::CStr;
+ use std::lazy::SyncLazy;
+ use std::sync::{Mutex, MutexGuard};
+
+ pub fn lock() -> MutexGuard<'static, Guard> {
+ static LOCK: SyncLazy<Mutex<Guard>> = SyncLazy::new(|| Mutex::new(Guard { _priv: () }));
+ LOCK.lock().unwrap()
+ }
- fn check_for_errors_in<T, F>(f: F) -> Result<T, String>
- where
- F: FnOnce() -> T,
- {
- use std::sync::{Mutex, Once};
- static INIT: Once = Once::new();
- static mut LOCK: *mut Mutex<()> = ptr::null_mut();
- unsafe {
- INIT.call_once(|| {
- LOCK = Box::into_raw(Box::new(Mutex::new(())));
- });
- // dlerror isn't thread safe, so we need to lock around this entire
- // sequence
- let _guard = (*LOCK).lock();
- let _old_error = libc::dlerror();
-
- let result = f();
-
- let last_error = libc::dlerror() as *const _;
- if ptr::null() == last_error {
- Ok(result)
- } else {
- let s = CStr::from_ptr(last_error).to_bytes();
- Err(str::from_utf8(s).unwrap().to_owned())
+ pub struct Guard {
+ _priv: (),
+ }
+
+ impl Guard {
+ pub fn get(&mut self) -> Result<(), String> {
+ let msg = unsafe { libc::dlerror() };
+ if msg.is_null() {
+ Ok(())
+ } else {
+ let msg = unsafe { CStr::from_ptr(msg as *const _) };
+ Err(msg.to_string_lossy().into_owned())
+ }
+ }
+
+ pub fn clear(&mut self) {
+ let _ = unsafe { libc::dlerror() };
}
}
}
+ pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
+ let s = CString::new(filename.as_bytes()).unwrap();
+
+ let mut dlerror = error::lock();
+ let ret = unsafe { libc::dlopen(s.as_ptr(), libc::RTLD_LAZY | libc::RTLD_LOCAL) };
+
+ if !ret.is_null() {
+ return Ok(ret.cast());
+ }
+
+ // A NULL return from `dlopen` indicates that an error has definitely occurred, so if
+ // nothing is in `dlerror`, we are racing with another thread that has stolen our error
+ // message. See the explanation on the `dl::error` module for more information.
+ dlerror.get().and_then(|()| Err("Unknown error".to_string()))
+ }
+
pub(super) unsafe fn symbol(
handle: *mut u8,
symbol: *const libc::c_char,
) -> Result<*mut u8, String> {
- check_for_errors_in(|| libc::dlsym(handle as *mut libc::c_void, symbol) as *mut u8)
+ let mut dlerror = error::lock();
+
+ // Unlike `dlopen`, it's possible for `dlsym` to return NULL without overwriting `dlerror`.
+ // Because of this, we clear `dlerror` before calling `dlsym` to avoid picking up a stale
+ // error message by accident.
+ dlerror.clear();
+
+ let ret = libc::dlsym(handle as *mut libc::c_void, symbol);
+
+ if !ret.is_null() {
+ return Ok(ret.cast());
+ }
+
+ // If `dlsym` returns NULL but there is nothing in `dlerror` it means one of two things:
+ // - We tried to load a symbol mapped to address 0. This is not technically an error but is
+ // unlikely to occur in practice and equally unlikely to be handled correctly by calling
+ // code. Therefore we treat it as an error anyway.
+ // - An error has occurred, but we are racing with another thread that has stolen our error
+ // message. See the explanation on the `dl::error` module for more information.
+ dlerror.get().and_then(|()| Err("Tried to load symbol mapped to address 0".to_string()))
}
pub(super) unsafe fn close(handle: *mut u8) {
#![feature(drain_filter)]
#![feature(in_band_lifetimes)]
#![feature(nll)]
+#![feature(once_cell)]
#![feature(or_patterns)]
#![feature(proc_macro_internals)]
#![feature(min_specialization)]
use rustc_span::Span;
use rustc_target::spec::{Target, TargetTriple};
-use flate2::read::DeflateDecoder;
+use snap::read::FrameDecoder;
use std::io::{Read, Result as IoResult, Write};
use std::ops::Deref;
use std::path::{Path, PathBuf};
let compressed_bytes = &buf[header_len..];
debug!("inflating {} bytes of compressed metadata", compressed_bytes.len());
let mut inflated = Vec::new();
- match DeflateDecoder::new(compressed_bytes).read_to_end(&mut inflated) {
+ match FrameDecoder::new(compressed_bytes).read_to_end(&mut inflated) {
Ok(_) => rustc_erase_owner!(OwningRef::new(inflated).map_owner_box()),
Err(_) => {
return Err(format!("failed to decompress metadata: {}", filename.display()));
#![feature(nll)]
#![feature(option_expect_none)]
#![feature(or_patterns)]
-#![feature(range_is_empty)]
#![feature(min_specialization)]
#![feature(trusted_len)]
#![feature(stmt_expr_attributes)]
// symbols. Other panic runtimes ensure that the relevant symbols are
// available to link things together, but they're never exercised.
match tcx.sess.panic_strategy() {
- PanicStrategy::Abort => lang_item != LangItem::EhPersonalityLangItem,
+ PanicStrategy::Abort => {
+ lang_item != LangItem::EhPersonality && lang_item != LangItem::EhCatchTypeinfo
+ }
PanicStrategy::Unwind => true,
}
}
self.to_unsigned_with_bit_width(64).map(|v| u64::try_from(v).unwrap())
}
+ /// Converts the scalar to produce an `u128`. Fails if the scalar is a pointer.
+ pub fn to_u128(self) -> InterpResult<'static, u128> {
+ self.to_unsigned_with_bit_width(128)
+ }
+
pub fn to_machine_usize(self, cx: &impl HasDataLayout) -> InterpResult<'static, u64> {
let b = self.to_bits(cx.data_layout().pointer_size)?;
Ok(u64::try_from(b).unwrap())
self.to_signed_with_bit_width(64).map(|v| i64::try_from(v).unwrap())
}
+ /// Converts the scalar to produce an `i128`. Fails if the scalar is a pointer.
+ pub fn to_i128(self) -> InterpResult<'static, i128> {
+ self.to_signed_with_bit_width(128)
+ }
+
pub fn to_machine_isize(self, cx: &impl HasDataLayout) -> InterpResult<'static, i64> {
let sz = cx.data_layout().pointer_size;
let b = self.to_bits(sz)?;
Nop,
}
+impl<'tcx> StatementKind<'tcx> {
+ pub fn as_assign_mut(&mut self) -> Option<&mut Box<(Place<'tcx>, Rvalue<'tcx>)>> {
+ match self {
+ StatementKind::Assign(x) => Some(x),
+ _ => None,
+ }
+ }
+}
+
/// Describes what kind of retag is to be performed.
#[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, HashStable)]
pub enum RetagKind {
/// ```
ConstantIndex {
/// index or -index (in Python terms), depending on from_end
- offset: u32,
+ offset: u64,
/// The thing being indexed must be at least this long. For arrays this
/// is always the exact length.
- min_length: u32,
+ min_length: u64,
/// Counting backwards from end? This is always false when indexing an
/// array.
from_end: bool,
/// If `from_end` is true `slice[from..slice.len() - to]`.
/// Otherwise `array[from..to]`.
Subslice {
- from: u32,
- to: u32,
+ from: u64,
+ to: u64,
/// Whether `to` counts from the start or end of the array/slice.
/// For `PlaceElem`s this is `true` if and only if the base is a slice.
/// For `ProjectionKind`, this can also be `true` for arrays.
// At least on 64 bit systems, `PlaceElem` should not be larger than two pointers.
#[cfg(target_arch = "x86_64")]
-static_assert_size!(PlaceElem<'_>, 16);
+static_assert_size!(PlaceElem<'_>, 24);
/// Alias for projections as they appear in `UserTypeProjection`, where we
/// need neither the `V` parameter for `Index` nor the `T` for `Field`.
})
}
+ pub fn is_move(&self) -> bool {
+ matches!(self, Operand::Move(..))
+ }
+
/// Convenience helper to make a literal-like constant from a given scalar value.
/// Since this is used to synthesize MIR, assumes `user_ty` is None.
pub fn const_from_scalar(
self.map_projections(|pat_ty_proj| pat_ty_proj.index())
}
- pub fn subslice(self, from: u32, to: u32) -> Self {
+ pub fn subslice(self, from: u64, to: u64) -> Self {
self.map_projections(|pat_ty_proj| pat_ty_proj.subslice(from, to))
}
self
}
- pub(crate) fn subslice(mut self, from: u32, to: u32) -> Self {
+ pub(crate) fn subslice(mut self, from: u64, to: u64) -> Self {
self.projs.push(ProjectionElem::Subslice { from, to, from_end: true });
self
}
desc { |tcx| "finding projection predicates for `{}`", tcx.def_path_str(key) }
}
+ query projection_ty_from_predicates(key: (DefId, DefId)) -> Option<ty::ProjectionTy<'tcx>> {
+ desc { |tcx| "finding projection type inside predicates of `{}`", tcx.def_path_str(key.0) }
+ }
+
query native_libraries(_: CrateNum) -> Lrc<Vec<NativeLib>> {
desc { "looking up the native libraries of a linked crate" }
}
use crate::ty::{self, Ty, TyCtxt};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items::{DerefMutTraitLangItem, DerefTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_macros::HashStable;
#[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, HashStable)]
impl<'tcx> OverloadedDeref<'tcx> {
pub fn method_call(&self, tcx: TyCtxt<'tcx>, source: Ty<'tcx>) -> (DefId, SubstsRef<'tcx>) {
let trait_def_id = match self.mutbl {
- hir::Mutability::Not => tcx.require_lang_item(DerefTraitLangItem, None),
- hir::Mutability::Mut => tcx.require_lang_item(DerefMutTraitLangItem, None),
+ hir::Mutability::Not => tcx.require_lang_item(LangItem::Deref, None),
+ hir::Mutability::Mut => tcx.require_lang_item(LangItem::DerefMut, None),
};
let method_def_id = tcx
.associated_items(trait_def_id)
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
use rustc_hir::definitions::{DefPathHash, Definitions};
use rustc_hir::intravisit::Visitor;
-use rustc_hir::lang_items::{self, PanicLocationLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{HirId, ItemKind, ItemLocalId, ItemLocalMap, ItemLocalSet, Node, TraitCandidate};
use rustc_index::vec::{Idx, IndexVec};
use rustc_macros::HashStable;
pub fn caller_location_ty(&self) -> Ty<'tcx> {
self.mk_imm_ref(
self.lifetimes.re_static,
- self.type_of(self.require_lang_item(PanicLocationLangItem, None))
+ self.type_of(self.require_lang_item(LangItem::PanicLocation, None))
.subst(*self, self.mk_substs([self.lifetimes.re_static.into()].iter())),
)
}
#[inline]
pub fn mk_box(self, ty: Ty<'tcx>) -> Ty<'tcx> {
- let def_id = self.require_lang_item(lang_items::OwnedBoxLangItem, None);
+ let def_id = self.require_lang_item(LangItem::OwnedBox, None);
self.mk_generic_adt(def_id, ty)
}
#[inline]
- pub fn mk_lang_item(self, ty: Ty<'tcx>, item: lang_items::LangItem) -> Option<Ty<'tcx>> {
+ pub fn mk_lang_item(self, ty: Ty<'tcx>, item: LangItem) -> Option<Ty<'tcx>> {
let def_id = self.lang_items().require(item).ok()?;
Some(self.mk_generic_adt(def_id, ty))
}
#[inline]
pub fn mk_maybe_uninit(self, ty: Ty<'tcx>) -> Ty<'tcx> {
- let def_id = self.require_lang_item(lang_items::MaybeUninitLangItem, None);
+ let def_id = self.require_lang_item(LangItem::MaybeUninit, None);
self.mk_generic_adt(def_id, ty)
}
use rustc_errors::ErrorReported;
use rustc_hir::def::Namespace;
use rustc_hir::def_id::{CrateNum, DefId};
-use rustc_hir::lang_items::{DropInPlaceFnLangItem, FnOnceTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_macros::HashStable;
use std::fmt;
}
pub fn resolve_drop_in_place(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ty::Instance<'tcx> {
- let def_id = tcx.require_lang_item(DropInPlaceFnLangItem, None);
+ let def_id = tcx.require_lang_item(LangItem::DropInPlace, None);
let substs = tcx.intern_substs(&[ty.into()]);
Instance::resolve(tcx, ty::ParamEnv::reveal_all(), def_id, substs).unwrap().unwrap()
}
substs: ty::SubstsRef<'tcx>,
) -> Instance<'tcx> {
debug!("fn_once_adapter_shim({:?}, {:?})", closure_did, substs);
- let fn_once = tcx.require_lang_item(FnOnceTraitLangItem, None);
+ let fn_once = tcx.require_lang_item(LangItem::FnOnce, None);
let call_once = tcx
.associated_items(fn_once)
.in_definition_order()
use rustc_attr as attr;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_hir as hir;
-use rustc_hir::lang_items::{GeneratorStateLangItem, PinTypeLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::{Idx, IndexVec};
use rustc_session::{DataTypeKind, FieldInfo, SizeKind, VariantInfo};
let env_region = ty::ReLateBound(ty::INNERMOST, ty::BrEnv);
let env_ty = tcx.mk_mut_ref(tcx.mk_region(env_region), ty);
- let pin_did = tcx.require_lang_item(PinTypeLangItem, None);
+ let pin_did = tcx.require_lang_item(LangItem::Pin, None);
let pin_adt_ref = tcx.adt_def(pin_did);
let pin_substs = tcx.intern_substs(&[env_ty.into()]);
let env_ty = tcx.mk_adt(pin_adt_ref, pin_substs);
sig.map_bound(|sig| {
- let state_did = tcx.require_lang_item(GeneratorStateLangItem, None);
+ let state_did = tcx.require_lang_item(LangItem::GeneratorState, None);
let state_adt_ref = tcx.adt_def(state_did);
let state_substs =
tcx.intern_substs(&[sig.yield_ty.into(), sig.return_ty.into()]);
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Namespace, Res};
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX};
-use rustc_hir::lang_items::{FnMutTraitLangItem, FnOnceTraitLangItem, FnTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{Constness, Node};
use rustc_index::vec::{Idx, IndexVec};
use rustc_macros::HashStable;
/// Upvar is captured by value. This is always true when the
/// closure is labeled `move`, but can also be true in other cases
/// depending on inference.
- ByValue,
+ ///
+ /// If the upvar was inferred to be captured by value (e.g. `move`
+ /// was not used), then the `Span` points to a usage that
+ /// required it. There may be more than one such usage
+ /// (e.g. `|| { a; a; }`), in which case we pick an
+ /// arbitrary one.
+ ByValue(Option<Span>),
/// Upvar is captured by reference.
ByRef(UpvarBorrow<'tcx>),
const NO_VARIANT_FLAGS = 0;
/// Indicates whether the field list of this variant is `#[non_exhaustive]`.
const IS_FIELD_LIST_NON_EXHAUSTIVE = 1 << 0;
+ /// Indicates whether this variant was obtained as part of recovering from
+ /// a syntactic error. May be incomplete or bogus.
+ const IS_RECOVERED = 1 << 1;
}
}
pub ctor_kind: CtorKind,
/// Flags of the variant (e.g. is field list non-exhaustive)?
flags: VariantFlags,
- /// Variant is obtained as part of recovering from a syntactic error.
- /// May be incomplete or bogus.
- pub recovered: bool,
}
impl<'tcx> VariantDef {
flags |= VariantFlags::IS_FIELD_LIST_NON_EXHAUSTIVE;
}
+ if recovered {
+ flags |= VariantFlags::IS_RECOVERED;
+ }
+
VariantDef {
def_id: variant_did.unwrap_or(parent_did),
ctor_def_id,
fields,
ctor_kind,
flags,
- recovered,
}
}
self.flags.intersects(VariantFlags::IS_FIELD_LIST_NON_EXHAUSTIVE)
}
+ /// Was this variant obtained as part of recovering from a syntactic error?
+ #[inline]
+ pub fn is_recovered(&self) -> bool {
+ self.flags.intersects(VariantFlags::IS_RECOVERED)
+ }
+
/// `repr(transparent)` structs can have a single non-ZST field, this function returns that
/// field.
pub fn transparent_newtype_field(&self, tcx: TyCtxt<'tcx>) -> Option<&FieldDef> {
pub fn trait_did(&self, tcx: TyCtxt<'tcx>) -> DefId {
match *self {
- ClosureKind::Fn => tcx.require_lang_item(FnTraitLangItem, None),
- ClosureKind::FnMut => tcx.require_lang_item(FnMutTraitLangItem, None),
- ClosureKind::FnOnce => tcx.require_lang_item(FnOnceTraitLangItem, None),
+ ClosureKind::Fn => tcx.require_lang_item(LangItem::Fn, None),
+ ClosureKind::FnMut => tcx.require_lang_item(LangItem::FnMut, None),
+ ClosureKind::FnOnce => tcx.require_lang_item(LangItem::FnOnce, None),
}
}
}
}
- let msg = ""; //FIXME: add "partially " or "collaterally "
+ let is_partial_move = move_site_vec.iter().any(|move_site| {
+ let move_out = self.move_data.moves[(*move_site).moi];
+ let moved_place = &self.move_data.move_paths[move_out.path].place;
+ // `*(_1)` where `_1` is a `Box` is actually a move out.
+ let is_box_move = moved_place.as_ref().projection == &[ProjectionElem::Deref]
+ && self.body.local_decls[moved_place.local].ty.is_box();
+
+ !is_box_move
+ && used_place != moved_place.as_ref()
+ && used_place.is_prefix_of(moved_place.as_ref())
+ });
+
+ let partial_str = if is_partial_move { "partial " } else { "" };
+ let partially_str = if is_partial_move { "partially " } else { "" };
let mut err = self.cannot_act_on_moved_value(
span,
desired_action.as_noun(),
- msg,
+ partially_str,
self.describe_place_with_options(moved_place, IncludingDowncast(true)),
);
self.add_moved_or_invoked_closure_note(location, used_place, &mut err);
let mut is_loop_move = false;
- let is_partial_move = move_site_vec.iter().any(|move_site| {
- let move_out = self.move_data.moves[(*move_site).moi];
- let moved_place = &self.move_data.move_paths[move_out.path].place;
- used_place != moved_place.as_ref() && used_place.is_prefix_of(moved_place.as_ref())
- });
+
for move_site in &move_site_vec {
let move_out = self.move_data.moves[(*move_site).moi];
let moved_place = &self.move_data.move_paths[move_out.path].place;
if location == move_out.source {
err.span_label(
span,
- format!("value moved{} here, in previous iteration of loop", move_msg),
+ format!(
+ "value {}moved{} here, in previous iteration of loop",
+ partially_str, move_msg
+ ),
);
is_loop_move = true;
} else if move_site.traversed_back_edge {
err.span_label(
move_span,
- format!("value moved{} here, in previous iteration of loop", move_msg),
+ format!(
+ "value {}moved{} here, in previous iteration of loop",
+ partially_str, move_msg
+ ),
);
} else {
if let UseSpans::FnSelfUse { var_span, fn_call_span, fn_span, kind } =
FnSelfUseKind::FnOnceCall => {
err.span_label(
fn_call_span,
- &format!("{} moved due to this call", place_name),
+ &format!(
+ "{} {}moved due to this call",
+ place_name, partially_str
+ ),
);
err.span_note(
var_span,
FnSelfUseKind::Operator { self_arg } => {
err.span_label(
fn_call_span,
- &format!("{} moved due to usage in operator", place_name),
+ &format!(
+ "{} {}moved due to usage in operator",
+ place_name, partially_str
+ ),
);
if self.fn_self_span_reported.insert(fn_span) {
err.span_note(
err.span_label(
fn_call_span,
&format!(
- "{} moved due to this implicit call to `.into_iter()`",
- place_name
+ "{} {}moved due to this implicit call to `.into_iter()`",
+ place_name, partially_str
),
);
} else {
err.span_label(
fn_call_span,
- &format!("{} moved due to this method call", place_name),
+ &format!(
+ "{} {}moved due to this method call",
+ place_name, partially_str
+ ),
);
}
// Avoid pointing to the same function in multiple different
}
}
} else {
- err.span_label(move_span, format!("value moved{} here", move_msg));
+ err.span_label(
+ move_span,
+ format!("value {}moved{} here", partially_str, move_msg),
+ );
move_spans.var_span_label(
&mut err,
- format!("variable moved due to use{}", move_spans.describe()),
+ format!(
+ "variable {}moved due to use{}",
+ partially_str,
+ move_spans.describe()
+ ),
);
}
}
err.span_label(
span,
format!(
- "value {} here {}",
+ "value {} here after {}move",
desired_action.as_verb_in_past_tense(),
- if is_partial_move { "after partial move" } else { "after move" },
+ partial_str
),
);
}
} else {
None
};
- self.note_type_does_not_implement_copy(&mut err, ¬e_msg, ty, span);
+ self.note_type_does_not_implement_copy(&mut err, ¬e_msg, ty, span, partial_str);
}
if let Some((_, mut old_err)) =
for moi in &self.move_data.loc_map[location] {
debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi);
- if mpis.contains(&self.move_data.moves[*moi].path) {
- debug!("report_use_of_moved_or_uninitialized: found");
+ let path = self.move_data.moves[*moi].path;
+ if mpis.contains(&path) {
+ debug!(
+ "report_use_of_moved_or_uninitialized: found {:?}",
+ move_paths[path].place
+ );
result.push(MoveSite { moi: *moi, traversed_back_edge: is_back_edge });
// Strictly speaking, we could continue our DFS here. There may be
place_desc: &str,
ty: Ty<'tcx>,
span: Option<Span>,
+ move_prefix: &str,
) {
let message = format!(
- "move occurs because {} has type `{}`, which does not implement the `Copy` trait",
- place_desc, ty,
+ "{}move occurs because {} has type `{}`, which does not implement the `Copy` trait",
+ move_prefix, place_desc, ty,
);
if let Some(span) = span {
err.span_label(span, message);
"closure_span: def_id={:?} target_place={:?} places={:?}",
def_id, target_place, places
);
- let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(def_id.as_local()?);
+ let local_did = def_id.as_local()?;
+ let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(local_did);
let expr = &self.infcx.tcx.hir().expect_expr(hir_id).kind;
debug!("closure_span: hir_id={:?} expr={:?}", hir_id, expr);
if let hir::ExprKind::Closure(.., body_id, args_span, _) = expr {
- for (upvar, place) in self.infcx.tcx.upvars_mentioned(def_id)?.values().zip(places) {
+ for ((upvar_hir_id, upvar), place) in
+ self.infcx.tcx.upvars_mentioned(def_id)?.iter().zip(places)
+ {
match place {
Operand::Copy(place) | Operand::Move(place)
if target_place == place.as_ref() =>
debug!("closure_span: found captured local {:?}", place);
let body = self.infcx.tcx.hir().body(*body_id);
let generator_kind = body.generator_kind();
- return Some((*args_span, generator_kind, upvar.span));
+ let upvar_id = ty::UpvarId {
+ var_path: ty::UpvarPath { hir_id: *upvar_hir_id },
+ closure_expr_id: local_did,
+ };
+
+ // If we have a more specific span available, point to that.
+ // We do this even though this span might be part of a borrow error
+ // message rather than a move error message. Our goal is to point
+ // to a span that shows why the upvar is used in the closure,
+ // so a move-related span is as good as any (and potentially better,
+ // if the overall error is due to a move of the upvar).
+ let usage_span =
+ match self.infcx.tcx.typeck(local_did).upvar_capture(upvar_id) {
+ ty::UpvarCapture::ByValue(Some(span)) => span,
+ _ => upvar.span,
+ };
+ return Some((*args_span, generator_kind, usage_span));
}
_ => {}
}
None => "value".to_string(),
};
- self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span));
+ self.note_type_does_not_implement_copy(
+ err,
+ &place_desc,
+ place_ty,
+ Some(span),
+ "",
+ );
} else {
binds_to.sort();
binds_to.dedup();
Some(desc) => format!("`{}`", desc),
None => "value".to_string(),
};
- self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span));
+ self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), "");
use_spans.args_span_label(err, format!("move out of {} occurs here", place_desc));
use_spans
&format!("`{}`", self.local_names[*local].unwrap()),
bind_to.ty,
Some(binding_span),
+ "",
);
}
}
let var_hir_id = upvar_id.var_path.hir_id;
let capture = tables.upvar_capture(*upvar_id);
let by_ref = match capture {
- ty::UpvarCapture::ByValue => false,
+ ty::UpvarCapture::ByValue(_) => false,
ty::UpvarCapture::ByRef(..) => true,
};
let mut upvar = Upvar {
desired_action: InitializationRequiringAction,
place_span: (PlaceRef<'tcx>, Span),
maybe_uninits: &BitSet<MovePathIndex>,
- from: u32,
- to: u32,
+ from: u64,
+ to: u64,
) {
if let Some(mpi) = self.move_path_for_place(place_span.0) {
let move_paths = &self.move_data.move_paths;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::lang_items::{CoerceUnsizedTraitLangItem, CopyTraitLangItem, SizedTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_index::vec::{Idx, IndexVec};
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_infer::infer::outlives::env::RegionBoundPairs;
if let PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) = context {
let tcx = self.tcx();
let trait_ref = ty::TraitRef {
- def_id: tcx.require_lang_item(CopyTraitLangItem, Some(self.last_span)),
+ def_id: tcx.require_lang_item(LangItem::Copy, Some(self.last_span)),
substs: tcx.mk_substs_trait(place_ty.ty, &[]),
};
PlaceTy::from_ty(match base_ty.kind {
ty::Array(inner, _) => {
assert!(!from_end, "array subslices should not use from_end");
- tcx.mk_array(inner, (to - from) as u64)
+ tcx.mk_array(inner, to - from)
}
ty::Slice(..) => {
assert!(from_end, "slice subslices should use from_end");
self.check_rvalue(body, rv, location);
if !self.tcx().features().unsized_locals {
let trait_ref = ty::TraitRef {
- def_id: tcx.require_lang_item(SizedTraitLangItem, Some(self.last_span)),
+ def_id: tcx.require_lang_item(LangItem::Sized, Some(self.last_span)),
substs: tcx.mk_substs_trait(place_ty, &[]),
};
self.prove_trait_ref(
self.param_env,
ty::Binder::bind(ty::TraitRef::new(
self.tcx().require_lang_item(
- CopyTraitLangItem,
+ LangItem::Copy,
Some(self.last_span),
),
tcx.mk_substs_trait(ty, &[]),
}
let trait_ref = ty::TraitRef {
- def_id: tcx.require_lang_item(SizedTraitLangItem, Some(self.last_span)),
+ def_id: tcx.require_lang_item(LangItem::Sized, Some(self.last_span)),
substs: tcx.mk_substs_trait(ty, &[]),
};
CastKind::Pointer(PointerCast::Unsize) => {
let &ty = ty;
let trait_ref = ty::TraitRef {
- def_id: tcx.require_lang_item(
- CoerceUnsizedTraitLangItem,
- Some(self.last_span),
- ),
+ def_id: tcx
+ .require_lang_item(LangItem::CoerceUnsized, Some(self.last_span)),
substs: tcx.mk_substs_trait(op.ty(body, tcx), &[ty.into()]),
};
use rustc_errors::DiagnosticBuilder;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{BodyOwnerKind, HirId};
use rustc_index::vec::{Idx, IndexVec};
use rustc_infer::infer::{InferCtxt, NLLRegionVariableOrigin};
if let DefiningTy::FnDef(def_id, _) = defining_ty {
if self.infcx.tcx.fn_sig(def_id).c_variadic() {
let va_list_did = self.infcx.tcx.require_lang_item(
- lang_items::VaListTypeLangItem,
+ LangItem::VaList,
Some(self.infcx.tcx.def_span(self.mir_def.did)),
);
let region = self
}
};
let base_ty = base_place.ty(self.builder.body, self.builder.tcx).ty;
- let len: u32 = match base_ty.kind {
+ let len: u64 = match base_ty.kind {
ty::Array(_, size) => {
let length = size.eval_usize(self.builder.tcx, self.builder.param_env);
length
use std::convert::TryFrom;
-use rustc_hir::lang_items::PanicLocationLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::mir::TerminatorKind;
use rustc_middle::ty::subst::Subst;
use rustc_span::{Span, Symbol};
// Allocate memory for `CallerLocation` struct.
let loc_ty = self
.tcx
- .type_of(self.tcx.require_lang_item(PanicLocationLangItem, None))
+ .type_of(self.tcx.require_lang_item(LangItem::PanicLocation, None))
.subst(*self.tcx, self.tcx.mk_substs([self.tcx.lifetimes.re_erased.into()].iter()));
let loc_layout = self.layout_of(loc_ty).unwrap();
let location = self.allocate(loc_layout, MemoryKind::CallerLocation);
ConstantIndex { offset, min_length, from_end } => {
let n = base.len(self)?;
- if n < u64::from(min_length) {
+ if n < min_length {
// This can only be reached in ConstProp and non-rustc-MIR.
throw_ub!(BoundsCheckFailed { len: min_length.into(), index: n });
}
let index = if from_end {
assert!(0 < offset && offset <= min_length);
- n.checked_sub(u64::from(offset)).unwrap()
+ n.checked_sub(offset).unwrap()
} else {
assert!(offset < min_length);
- u64::from(offset)
+ offset
};
self.mplace_index(base, index)?
#![feature(try_blocks)]
#![feature(associated_type_bounds)]
#![feature(associated_type_defaults)]
-#![feature(range_is_empty)]
#![feature(stmt_expr_attributes)]
#![feature(trait_alias)]
#![feature(option_expect_none)]
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
use rustc_hir::itemlikevisit::ItemLikeVisitor;
-use rustc_hir::lang_items::{ExchangeMallocFnLangItem, StartFnLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_index::bit_set::GrowableBitSet;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::interpret::{AllocId, ConstValue};
mir::Rvalue::NullaryOp(mir::NullOp::Box, _) => {
let tcx = self.tcx;
let exchange_malloc_fn_def_id =
- tcx.require_lang_item(ExchangeMallocFnLangItem, None);
+ tcx.require_lang_item(LangItem::ExchangeMalloc, None);
let instance = Instance::mono(tcx, exchange_malloc_fn_def_id);
if should_codegen_locally(tcx, &instance) {
self.output.push(create_fn_mono_item(self.tcx, instance, span));
_ => return,
};
- let start_def_id = match self.tcx.lang_items().require(StartFnLangItem) {
+ let start_def_id = match self.tcx.lang_items().require(LangItem::Start) {
Ok(s) => s,
Err(err) => self.tcx.sess.fatal(&err),
};
use rustc_middle::ty::adjustment::CustomCoerceUnsized;
use rustc_middle::ty::{self, Ty, TyCtxt};
-use rustc_hir::lang_items::CoerceUnsizedTraitLangItem;
+use rustc_hir::lang_items::LangItem;
pub mod collector;
pub mod partitioning;
source_ty: Ty<'tcx>,
target_ty: Ty<'tcx>,
) -> CustomCoerceUnsized {
- let def_id = tcx.require_lang_item(CoerceUnsizedTraitLangItem, None);
+ let def_id = tcx.require_lang_item(LangItem::CoerceUnsized, None);
let trait_ref = ty::Binder::bind(ty::TraitRef {
def_id,
+++ /dev/null
-//! Partitioning Codegen Units for Incremental Compilation
-//! ======================================================
-//!
-//! The task of this module is to take the complete set of monomorphizations of
-//! a crate and produce a set of codegen units from it, where a codegen unit
-//! is a named set of (mono-item, linkage) pairs. That is, this module
-//! decides which monomorphization appears in which codegen units with which
-//! linkage. The following paragraphs describe some of the background on the
-//! partitioning scheme.
-//!
-//! The most important opportunity for saving on compilation time with
-//! incremental compilation is to avoid re-codegenning and re-optimizing code.
-//! Since the unit of codegen and optimization for LLVM is "modules" or, how
-//! we call them "codegen units", the particulars of how much time can be saved
-//! by incremental compilation are tightly linked to how the output program is
-//! partitioned into these codegen units prior to passing it to LLVM --
-//! especially because we have to treat codegen units as opaque entities once
-//! they are created: There is no way for us to incrementally update an existing
-//! LLVM module and so we have to build any such module from scratch if it was
-//! affected by some change in the source code.
-//!
-//! From that point of view it would make sense to maximize the number of
-//! codegen units by, for example, putting each function into its own module.
-//! That way only those modules would have to be re-compiled that were actually
-//! affected by some change, minimizing the number of functions that could have
-//! been re-used but just happened to be located in a module that is
-//! re-compiled.
-//!
-//! However, since LLVM optimization does not work across module boundaries,
-//! using such a highly granular partitioning would lead to very slow runtime
-//! code since it would effectively prohibit inlining and other inter-procedure
-//! optimizations. We want to avoid that as much as possible.
-//!
-//! Thus we end up with a trade-off: The bigger the codegen units, the better
-//! LLVM's optimizer can do its work, but also the smaller the compilation time
-//! reduction we get from incremental compilation.
-//!
-//! Ideally, we would create a partitioning such that there are few big codegen
-//! units with few interdependencies between them. For now though, we use the
-//! following heuristic to determine the partitioning:
-//!
-//! - There are two codegen units for every source-level module:
-//! - One for "stable", that is non-generic, code
-//! - One for more "volatile" code, i.e., monomorphized instances of functions
-//! defined in that module
-//!
-//! In order to see why this heuristic makes sense, let's take a look at when a
-//! codegen unit can get invalidated:
-//!
-//! 1. The most straightforward case is when the BODY of a function or global
-//! changes. Then any codegen unit containing the code for that item has to be
-//! re-compiled. Note that this includes all codegen units where the function
-//! has been inlined.
-//!
-//! 2. The next case is when the SIGNATURE of a function or global changes. In
-//! this case, all codegen units containing a REFERENCE to that item have to be
-//! re-compiled. This is a superset of case 1.
-//!
-//! 3. The final and most subtle case is when a REFERENCE to a generic function
-//! is added or removed somewhere. Even though the definition of the function
-//! might be unchanged, a new REFERENCE might introduce a new monomorphized
-//! instance of this function which has to be placed and compiled somewhere.
-//! Conversely, when removing a REFERENCE, it might have been the last one with
-//! that particular set of generic arguments and thus we have to remove it.
-//!
-//! From the above we see that just using one codegen unit per source-level
-//! module is not such a good idea, since just adding a REFERENCE to some
-//! generic item somewhere else would invalidate everything within the module
-//! containing the generic item. The heuristic above reduces this detrimental
-//! side-effect of references a little by at least not touching the non-generic
-//! code of the module.
-//!
-//! A Note on Inlining
-//! ------------------
-//! As briefly mentioned above, in order for LLVM to be able to inline a
-//! function call, the body of the function has to be available in the LLVM
-//! module where the call is made. This has a few consequences for partitioning:
-//!
-//! - The partitioning algorithm has to take care of placing functions into all
-//! codegen units where they should be available for inlining. It also has to
-//! decide on the correct linkage for these functions.
-//!
-//! - The partitioning algorithm has to know which functions are likely to get
-//! inlined, so it can distribute function instantiations accordingly. Since
-//! there is no way of knowing for sure which functions LLVM will decide to
-//! inline in the end, we apply a heuristic here: Only functions marked with
-//! `#[inline]` are considered for inlining by the partitioner. The current
-//! implementation will not try to determine if a function is likely to be
-//! inlined by looking at the functions definition.
-//!
-//! Note though that as a side-effect of creating a codegen units per
-//! source-level module, functions from the same module will be available for
-//! inlining, even when they are not marked `#[inline]`.
-
-use std::cmp;
-use std::collections::hash_map::Entry;
-
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_data_structures::sync;
-use rustc_hir::def::DefKind;
-use rustc_hir::def_id::{CrateNum, DefId, DefIdSet, CRATE_DEF_INDEX, LOCAL_CRATE};
-use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
-use rustc_middle::middle::exported_symbols::SymbolExportLevel;
-use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, Linkage, Visibility};
-use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
-use rustc_middle::ty::print::characteristic_def_id_of_type;
-use rustc_middle::ty::query::Providers;
-use rustc_middle::ty::{self, DefIdTree, InstanceDef, TyCtxt};
-use rustc_span::symbol::{Symbol, SymbolStr};
-
-use crate::monomorphize::collector::InliningMap;
-use crate::monomorphize::collector::{self, MonoItemCollectionMode};
-
-// Anything we can't find a proper codegen unit for goes into this.
-fn fallback_cgu_name(name_builder: &mut CodegenUnitNameBuilder<'_>) -> Symbol {
- name_builder.build_cgu_name(LOCAL_CRATE, &["fallback"], Some("cgu"))
-}
-
-pub fn partition<'tcx, I>(
- tcx: TyCtxt<'tcx>,
- mono_items: I,
- max_cgu_count: usize,
- inlining_map: &InliningMap<'tcx>,
-) -> Vec<CodegenUnit<'tcx>>
-where
- I: Iterator<Item = MonoItem<'tcx>>,
-{
- let _prof_timer = tcx.prof.generic_activity("cgu_partitioning");
-
- // In the first step, we place all regular monomorphizations into their
- // respective 'home' codegen unit. Regular monomorphizations are all
- // functions and statics defined in the local crate.
- let mut initial_partitioning = {
- let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_roots");
- place_root_mono_items(tcx, mono_items)
- };
-
- initial_partitioning.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
-
- debug_dump(tcx, "INITIAL PARTITIONING:", initial_partitioning.codegen_units.iter());
-
- // Merge until we have at most `max_cgu_count` codegen units.
- {
- let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_merge_cgus");
- merge_codegen_units(tcx, &mut initial_partitioning, max_cgu_count);
- debug_dump(tcx, "POST MERGING:", initial_partitioning.codegen_units.iter());
- }
-
- // In the next step, we use the inlining map to determine which additional
- // monomorphizations have to go into each codegen unit. These additional
- // monomorphizations can be drop-glue, functions from external crates, and
- // local functions the definition of which is marked with `#[inline]`.
- let mut post_inlining = {
- let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_inline_items");
- place_inlined_mono_items(initial_partitioning, inlining_map)
- };
-
- post_inlining.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
-
- debug_dump(tcx, "POST INLINING:", post_inlining.codegen_units.iter());
-
- // Next we try to make as many symbols "internal" as possible, so LLVM has
- // more freedom to optimize.
- if tcx.sess.opts.cg.link_dead_code != Some(true) {
- let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
- internalize_symbols(tcx, &mut post_inlining, inlining_map);
- }
-
- // Finally, sort by codegen unit name, so that we get deterministic results.
- let PostInliningPartitioning {
- codegen_units: mut result,
- mono_item_placements: _,
- internalization_candidates: _,
- } = post_inlining;
-
- result.sort_by_cached_key(|cgu| cgu.name().as_str());
-
- result
-}
-
-struct PreInliningPartitioning<'tcx> {
- codegen_units: Vec<CodegenUnit<'tcx>>,
- roots: FxHashSet<MonoItem<'tcx>>,
- internalization_candidates: FxHashSet<MonoItem<'tcx>>,
-}
-
-/// For symbol internalization, we need to know whether a symbol/mono-item is
-/// accessed from outside the codegen unit it is defined in. This type is used
-/// to keep track of that.
-#[derive(Clone, PartialEq, Eq, Debug)]
-enum MonoItemPlacement {
- SingleCgu { cgu_name: Symbol },
- MultipleCgus,
-}
-
-struct PostInliningPartitioning<'tcx> {
- codegen_units: Vec<CodegenUnit<'tcx>>,
- mono_item_placements: FxHashMap<MonoItem<'tcx>, MonoItemPlacement>,
- internalization_candidates: FxHashSet<MonoItem<'tcx>>,
-}
-
-fn place_root_mono_items<'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I) -> PreInliningPartitioning<'tcx>
-where
- I: Iterator<Item = MonoItem<'tcx>>,
-{
- let mut roots = FxHashSet::default();
- let mut codegen_units = FxHashMap::default();
- let is_incremental_build = tcx.sess.opts.incremental.is_some();
- let mut internalization_candidates = FxHashSet::default();
-
- // Determine if monomorphizations instantiated in this crate will be made
- // available to downstream crates. This depends on whether we are in
- // share-generics mode and whether the current crate can even have
- // downstream crates.
- let export_generics = tcx.sess.opts.share_generics() && tcx.local_crate_exports_generics();
-
- let cgu_name_builder = &mut CodegenUnitNameBuilder::new(tcx);
- let cgu_name_cache = &mut FxHashMap::default();
-
- for mono_item in mono_items {
- match mono_item.instantiation_mode(tcx) {
- InstantiationMode::GloballyShared { .. } => {}
- InstantiationMode::LocalCopy => continue,
- }
-
- let characteristic_def_id = characteristic_def_id_of_mono_item(tcx, mono_item);
- let is_volatile = is_incremental_build && mono_item.is_generic_fn();
-
- let codegen_unit_name = match characteristic_def_id {
- Some(def_id) => compute_codegen_unit_name(
- tcx,
- cgu_name_builder,
- def_id,
- is_volatile,
- cgu_name_cache,
- ),
- None => fallback_cgu_name(cgu_name_builder),
- };
-
- let codegen_unit = codegen_units
- .entry(codegen_unit_name)
- .or_insert_with(|| CodegenUnit::new(codegen_unit_name));
-
- let mut can_be_internalized = true;
- let (linkage, visibility) = mono_item_linkage_and_visibility(
- tcx,
- &mono_item,
- &mut can_be_internalized,
- export_generics,
- );
- if visibility == Visibility::Hidden && can_be_internalized {
- internalization_candidates.insert(mono_item);
- }
-
- codegen_unit.items_mut().insert(mono_item, (linkage, visibility));
- roots.insert(mono_item);
- }
-
- // Always ensure we have at least one CGU; otherwise, if we have a
- // crate with just types (for example), we could wind up with no CGU.
- if codegen_units.is_empty() {
- let codegen_unit_name = fallback_cgu_name(cgu_name_builder);
- codegen_units.insert(codegen_unit_name, CodegenUnit::new(codegen_unit_name));
- }
-
- PreInliningPartitioning {
- codegen_units: codegen_units.into_iter().map(|(_, codegen_unit)| codegen_unit).collect(),
- roots,
- internalization_candidates,
- }
-}
-
-fn mono_item_linkage_and_visibility(
- tcx: TyCtxt<'tcx>,
- mono_item: &MonoItem<'tcx>,
- can_be_internalized: &mut bool,
- export_generics: bool,
-) -> (Linkage, Visibility) {
- if let Some(explicit_linkage) = mono_item.explicit_linkage(tcx) {
- return (explicit_linkage, Visibility::Default);
- }
- let vis = mono_item_visibility(tcx, mono_item, can_be_internalized, export_generics);
- (Linkage::External, vis)
-}
-
-fn mono_item_visibility(
- tcx: TyCtxt<'tcx>,
- mono_item: &MonoItem<'tcx>,
- can_be_internalized: &mut bool,
- export_generics: bool,
-) -> Visibility {
- let instance = match mono_item {
- // This is pretty complicated; see below.
- MonoItem::Fn(instance) => instance,
-
- // Misc handling for generics and such, but otherwise:
- MonoItem::Static(def_id) => {
- return if tcx.is_reachable_non_generic(*def_id) {
- *can_be_internalized = false;
- default_visibility(tcx, *def_id, false)
- } else {
- Visibility::Hidden
- };
- }
- MonoItem::GlobalAsm(hir_id) => {
- let def_id = tcx.hir().local_def_id(*hir_id);
- return if tcx.is_reachable_non_generic(def_id) {
- *can_be_internalized = false;
- default_visibility(tcx, def_id.to_def_id(), false)
- } else {
- Visibility::Hidden
- };
- }
- };
-
- let def_id = match instance.def {
- InstanceDef::Item(def) => def.did,
- InstanceDef::DropGlue(def_id, Some(_)) => def_id,
-
- // These are all compiler glue and such, never exported, always hidden.
- InstanceDef::VtableShim(..)
- | InstanceDef::ReifyShim(..)
- | InstanceDef::FnPtrShim(..)
- | InstanceDef::Virtual(..)
- | InstanceDef::Intrinsic(..)
- | InstanceDef::ClosureOnceShim { .. }
- | InstanceDef::DropGlue(..)
- | InstanceDef::CloneShim(..) => return Visibility::Hidden,
- };
-
- // The `start_fn` lang item is actually a monomorphized instance of a
- // function in the standard library, used for the `main` function. We don't
- // want to export it so we tag it with `Hidden` visibility but this symbol
- // is only referenced from the actual `main` symbol which we unfortunately
- // don't know anything about during partitioning/collection. As a result we
- // forcibly keep this symbol out of the `internalization_candidates` set.
- //
- // FIXME: eventually we don't want to always force this symbol to have
- // hidden visibility, it should indeed be a candidate for
- // internalization, but we have to understand that it's referenced
- // from the `main` symbol we'll generate later.
- //
- // This may be fixable with a new `InstanceDef` perhaps? Unsure!
- if tcx.lang_items().start_fn() == Some(def_id) {
- *can_be_internalized = false;
- return Visibility::Hidden;
- }
-
- let is_generic = instance.substs.non_erasable_generics().next().is_some();
-
- // Upstream `DefId` instances get different handling than local ones.
- if !def_id.is_local() {
- return if export_generics && is_generic {
- // If it is a upstream monomorphization and we export generics, we must make
- // it available to downstream crates.
- *can_be_internalized = false;
- default_visibility(tcx, def_id, true)
- } else {
- Visibility::Hidden
- };
- }
-
- if is_generic {
- if export_generics {
- if tcx.is_unreachable_local_definition(def_id) {
- // This instance cannot be used from another crate.
- Visibility::Hidden
- } else {
- // This instance might be useful in a downstream crate.
- *can_be_internalized = false;
- default_visibility(tcx, def_id, true)
- }
- } else {
- // We are not exporting generics or the definition is not reachable
- // for downstream crates, we can internalize its instantiations.
- Visibility::Hidden
- }
- } else {
- // If this isn't a generic function then we mark this a `Default` if
- // this is a reachable item, meaning that it's a symbol other crates may
- // access when they link to us.
- if tcx.is_reachable_non_generic(def_id) {
- *can_be_internalized = false;
- debug_assert!(!is_generic);
- return default_visibility(tcx, def_id, false);
- }
-
- // If this isn't reachable then we're gonna tag this with `Hidden`
- // visibility. In some situations though we'll want to prevent this
- // symbol from being internalized.
- //
- // There's two categories of items here:
- //
- // * First is weak lang items. These are basically mechanisms for
- // libcore to forward-reference symbols defined later in crates like
- // the standard library or `#[panic_handler]` definitions. The
- // definition of these weak lang items needs to be referenceable by
- // libcore, so we're no longer a candidate for internalization.
- // Removal of these functions can't be done by LLVM but rather must be
- // done by the linker as it's a non-local decision.
- //
- // * Second is "std internal symbols". Currently this is primarily used
- // for allocator symbols. Allocators are a little weird in their
- // implementation, but the idea is that the compiler, at the last
- // minute, defines an allocator with an injected object file. The
- // `alloc` crate references these symbols (`__rust_alloc`) and the
- // definition doesn't get hooked up until a linked crate artifact is
- // generated.
- //
- // The symbols synthesized by the compiler (`__rust_alloc`) are thin
- // veneers around the actual implementation, some other symbol which
- // implements the same ABI. These symbols (things like `__rg_alloc`,
- // `__rdl_alloc`, `__rde_alloc`, etc), are all tagged with "std
- // internal symbols".
- //
- // The std-internal symbols here **should not show up in a dll as an
- // exported interface**, so they return `false` from
- // `is_reachable_non_generic` above and we'll give them `Hidden`
- // visibility below. Like the weak lang items, though, we can't let
- // LLVM internalize them as this decision is left up to the linker to
- // omit them, so prevent them from being internalized.
- let attrs = tcx.codegen_fn_attrs(def_id);
- if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) {
- *can_be_internalized = false;
- }
-
- Visibility::Hidden
- }
-}
-
-fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibility {
- if !tcx.sess.target.target.options.default_hidden_visibility {
- return Visibility::Default;
- }
-
- // Generic functions never have export-level C.
- if is_generic {
- return Visibility::Hidden;
- }
-
- // Things with export level C don't get instantiated in
- // downstream crates.
- if !id.is_local() {
- return Visibility::Hidden;
- }
-
- // C-export level items remain at `Default`, all other internal
- // items become `Hidden`.
- match tcx.reachable_non_generics(id.krate).get(&id) {
- Some(SymbolExportLevel::C) => Visibility::Default,
- _ => Visibility::Hidden,
- }
-}
-
-fn merge_codegen_units<'tcx>(
- tcx: TyCtxt<'tcx>,
- initial_partitioning: &mut PreInliningPartitioning<'tcx>,
- target_cgu_count: usize,
-) {
- assert!(target_cgu_count >= 1);
- let codegen_units = &mut initial_partitioning.codegen_units;
-
- // Note that at this point in time the `codegen_units` here may not be in a
- // deterministic order (but we know they're deterministically the same set).
- // We want this merging to produce a deterministic ordering of codegen units
- // from the input.
- //
- // Due to basically how we've implemented the merging below (merge the two
- // smallest into each other) we're sure to start off with a deterministic
- // order (sorted by name). This'll mean that if two cgus have the same size
- // the stable sort below will keep everything nice and deterministic.
- codegen_units.sort_by_cached_key(|cgu| cgu.name().as_str());
-
- // This map keeps track of what got merged into what.
- let mut cgu_contents: FxHashMap<Symbol, Vec<SymbolStr>> =
- codegen_units.iter().map(|cgu| (cgu.name(), vec![cgu.name().as_str()])).collect();
-
- // Merge the two smallest codegen units until the target size is reached.
- while codegen_units.len() > target_cgu_count {
- // Sort small cgus to the back
- codegen_units.sort_by_cached_key(|cgu| cmp::Reverse(cgu.size_estimate()));
- let mut smallest = codegen_units.pop().unwrap();
- let second_smallest = codegen_units.last_mut().unwrap();
-
- // Move the mono-items from `smallest` to `second_smallest`
- second_smallest.modify_size_estimate(smallest.size_estimate());
- for (k, v) in smallest.items_mut().drain() {
- second_smallest.items_mut().insert(k, v);
- }
-
- // Record that `second_smallest` now contains all the stuff that was in
- // `smallest` before.
- let mut consumed_cgu_names = cgu_contents.remove(&smallest.name()).unwrap();
- cgu_contents.get_mut(&second_smallest.name()).unwrap().extend(consumed_cgu_names.drain(..));
-
- debug!(
- "CodegenUnit {} merged into CodegenUnit {}",
- smallest.name(),
- second_smallest.name()
- );
- }
-
- let cgu_name_builder = &mut CodegenUnitNameBuilder::new(tcx);
-
- if tcx.sess.opts.incremental.is_some() {
- // If we are doing incremental compilation, we want CGU names to
- // reflect the path of the source level module they correspond to.
- // For CGUs that contain the code of multiple modules because of the
- // merging done above, we use a concatenation of the names of
- // all contained CGUs.
- let new_cgu_names: FxHashMap<Symbol, String> = cgu_contents
- .into_iter()
- // This `filter` makes sure we only update the name of CGUs that
- // were actually modified by merging.
- .filter(|(_, cgu_contents)| cgu_contents.len() > 1)
- .map(|(current_cgu_name, cgu_contents)| {
- let mut cgu_contents: Vec<&str> = cgu_contents.iter().map(|s| &s[..]).collect();
-
- // Sort the names, so things are deterministic and easy to
- // predict.
- cgu_contents.sort();
-
- (current_cgu_name, cgu_contents.join("--"))
- })
- .collect();
-
- for cgu in codegen_units.iter_mut() {
- if let Some(new_cgu_name) = new_cgu_names.get(&cgu.name()) {
- if tcx.sess.opts.debugging_opts.human_readable_cgu_names {
- cgu.set_name(Symbol::intern(&new_cgu_name));
- } else {
- // If we don't require CGU names to be human-readable, we
- // use a fixed length hash of the composite CGU name
- // instead.
- let new_cgu_name = CodegenUnit::mangle_name(&new_cgu_name);
- cgu.set_name(Symbol::intern(&new_cgu_name));
- }
- }
- }
- } else {
- // If we are compiling non-incrementally we just generate simple CGU
- // names containing an index.
- for (index, cgu) in codegen_units.iter_mut().enumerate() {
- cgu.set_name(numbered_codegen_unit_name(cgu_name_builder, index));
- }
- }
-}
-
-fn place_inlined_mono_items<'tcx>(
- initial_partitioning: PreInliningPartitioning<'tcx>,
- inlining_map: &InliningMap<'tcx>,
-) -> PostInliningPartitioning<'tcx> {
- let mut new_partitioning = Vec::new();
- let mut mono_item_placements = FxHashMap::default();
-
- let PreInliningPartitioning { codegen_units: initial_cgus, roots, internalization_candidates } =
- initial_partitioning;
-
- let single_codegen_unit = initial_cgus.len() == 1;
-
- for old_codegen_unit in initial_cgus {
- // Collect all items that need to be available in this codegen unit.
- let mut reachable = FxHashSet::default();
- for root in old_codegen_unit.items().keys() {
- follow_inlining(*root, inlining_map, &mut reachable);
- }
-
- let mut new_codegen_unit = CodegenUnit::new(old_codegen_unit.name());
-
- // Add all monomorphizations that are not already there.
- for mono_item in reachable {
- if let Some(linkage) = old_codegen_unit.items().get(&mono_item) {
- // This is a root, just copy it over.
- new_codegen_unit.items_mut().insert(mono_item, *linkage);
- } else {
- if roots.contains(&mono_item) {
- bug!(
- "GloballyShared mono-item inlined into other CGU: \
- {:?}",
- mono_item
- );
- }
-
- // This is a CGU-private copy.
- new_codegen_unit
- .items_mut()
- .insert(mono_item, (Linkage::Internal, Visibility::Default));
- }
-
- if !single_codegen_unit {
- // If there is more than one codegen unit, we need to keep track
- // in which codegen units each monomorphization is placed.
- match mono_item_placements.entry(mono_item) {
- Entry::Occupied(e) => {
- let placement = e.into_mut();
- debug_assert!(match *placement {
- MonoItemPlacement::SingleCgu { cgu_name } => {
- cgu_name != new_codegen_unit.name()
- }
- MonoItemPlacement::MultipleCgus => true,
- });
- *placement = MonoItemPlacement::MultipleCgus;
- }
- Entry::Vacant(e) => {
- e.insert(MonoItemPlacement::SingleCgu {
- cgu_name: new_codegen_unit.name(),
- });
- }
- }
- }
- }
-
- new_partitioning.push(new_codegen_unit);
- }
-
- return PostInliningPartitioning {
- codegen_units: new_partitioning,
- mono_item_placements,
- internalization_candidates,
- };
-
- fn follow_inlining<'tcx>(
- mono_item: MonoItem<'tcx>,
- inlining_map: &InliningMap<'tcx>,
- visited: &mut FxHashSet<MonoItem<'tcx>>,
- ) {
- if !visited.insert(mono_item) {
- return;
- }
-
- inlining_map.with_inlining_candidates(mono_item, |target| {
- follow_inlining(target, inlining_map, visited);
- });
- }
-}
-
-fn internalize_symbols<'tcx>(
- _tcx: TyCtxt<'tcx>,
- partitioning: &mut PostInliningPartitioning<'tcx>,
- inlining_map: &InliningMap<'tcx>,
-) {
- if partitioning.codegen_units.len() == 1 {
- // Fast path for when there is only one codegen unit. In this case we
- // can internalize all candidates, since there is nowhere else they
- // could be accessed from.
- for cgu in &mut partitioning.codegen_units {
- for candidate in &partitioning.internalization_candidates {
- cgu.items_mut().insert(*candidate, (Linkage::Internal, Visibility::Default));
- }
- }
-
- return;
- }
-
- // Build a map from every monomorphization to all the monomorphizations that
- // reference it.
- let mut accessor_map: FxHashMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>> = Default::default();
- inlining_map.iter_accesses(|accessor, accessees| {
- for accessee in accessees {
- accessor_map.entry(*accessee).or_default().push(accessor);
- }
- });
-
- let mono_item_placements = &partitioning.mono_item_placements;
-
- // For each internalization candidates in each codegen unit, check if it is
- // accessed from outside its defining codegen unit.
- for cgu in &mut partitioning.codegen_units {
- let home_cgu = MonoItemPlacement::SingleCgu { cgu_name: cgu.name() };
-
- for (accessee, linkage_and_visibility) in cgu.items_mut() {
- if !partitioning.internalization_candidates.contains(accessee) {
- // This item is no candidate for internalizing, so skip it.
- continue;
- }
- debug_assert_eq!(mono_item_placements[accessee], home_cgu);
-
- if let Some(accessors) = accessor_map.get(accessee) {
- if accessors
- .iter()
- .filter_map(|accessor| {
- // Some accessors might not have been
- // instantiated. We can safely ignore those.
- mono_item_placements.get(accessor)
- })
- .any(|placement| *placement != home_cgu)
- {
- // Found an accessor from another CGU, so skip to the next
- // item without marking this one as internal.
- continue;
- }
- }
-
- // If we got here, we did not find any accesses from other CGUs,
- // so it's fine to make this monomorphization internal.
- *linkage_and_visibility = (Linkage::Internal, Visibility::Default);
- }
- }
-}
-
-fn characteristic_def_id_of_mono_item<'tcx>(
- tcx: TyCtxt<'tcx>,
- mono_item: MonoItem<'tcx>,
-) -> Option<DefId> {
- match mono_item {
- MonoItem::Fn(instance) => {
- let def_id = match instance.def {
- ty::InstanceDef::Item(def) => def.did,
- ty::InstanceDef::VtableShim(..)
- | ty::InstanceDef::ReifyShim(..)
- | ty::InstanceDef::FnPtrShim(..)
- | ty::InstanceDef::ClosureOnceShim { .. }
- | ty::InstanceDef::Intrinsic(..)
- | ty::InstanceDef::DropGlue(..)
- | ty::InstanceDef::Virtual(..)
- | ty::InstanceDef::CloneShim(..) => return None,
- };
-
- // If this is a method, we want to put it into the same module as
- // its self-type. If the self-type does not provide a characteristic
- // DefId, we use the location of the impl after all.
-
- if tcx.trait_of_item(def_id).is_some() {
- let self_ty = instance.substs.type_at(0);
- // This is a default implementation of a trait method.
- return characteristic_def_id_of_type(self_ty).or(Some(def_id));
- }
-
- if let Some(impl_def_id) = tcx.impl_of_method(def_id) {
- if tcx.sess.opts.incremental.is_some()
- && tcx.trait_id_of_impl(impl_def_id) == tcx.lang_items().drop_trait()
- {
- // Put `Drop::drop` into the same cgu as `drop_in_place`
- // since `drop_in_place` is the only thing that can
- // call it.
- return None;
- }
- // This is a method within an impl, find out what the self-type is:
- let impl_self_ty = tcx.subst_and_normalize_erasing_regions(
- instance.substs,
- ty::ParamEnv::reveal_all(),
- &tcx.type_of(impl_def_id),
- );
- if let Some(def_id) = characteristic_def_id_of_type(impl_self_ty) {
- return Some(def_id);
- }
- }
-
- Some(def_id)
- }
- MonoItem::Static(def_id) => Some(def_id),
- MonoItem::GlobalAsm(hir_id) => Some(tcx.hir().local_def_id(hir_id).to_def_id()),
- }
-}
-
-type CguNameCache = FxHashMap<(DefId, bool), Symbol>;
-
-fn compute_codegen_unit_name(
- tcx: TyCtxt<'_>,
- name_builder: &mut CodegenUnitNameBuilder<'_>,
- def_id: DefId,
- volatile: bool,
- cache: &mut CguNameCache,
-) -> Symbol {
- // Find the innermost module that is not nested within a function.
- let mut current_def_id = def_id;
- let mut cgu_def_id = None;
- // Walk backwards from the item we want to find the module for.
- loop {
- if current_def_id.index == CRATE_DEF_INDEX {
- if cgu_def_id.is_none() {
- // If we have not found a module yet, take the crate root.
- cgu_def_id = Some(DefId { krate: def_id.krate, index: CRATE_DEF_INDEX });
- }
- break;
- } else if tcx.def_kind(current_def_id) == DefKind::Mod {
- if cgu_def_id.is_none() {
- cgu_def_id = Some(current_def_id);
- }
- } else {
- // If we encounter something that is not a module, throw away
- // any module that we've found so far because we now know that
- // it is nested within something else.
- cgu_def_id = None;
- }
-
- current_def_id = tcx.parent(current_def_id).unwrap();
- }
-
- let cgu_def_id = cgu_def_id.unwrap();
-
- *cache.entry((cgu_def_id, volatile)).or_insert_with(|| {
- let def_path = tcx.def_path(cgu_def_id);
-
- let components = def_path.data.iter().map(|part| part.data.as_symbol());
-
- let volatile_suffix = volatile.then_some("volatile");
-
- name_builder.build_cgu_name(def_path.krate, components, volatile_suffix)
- })
-}
-
-fn numbered_codegen_unit_name(
- name_builder: &mut CodegenUnitNameBuilder<'_>,
- index: usize,
-) -> Symbol {
- name_builder.build_cgu_name_no_mangle(LOCAL_CRATE, &["cgu"], Some(index))
-}
-
-fn debug_dump<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, label: &str, cgus: I)
-where
- I: Iterator<Item = &'a CodegenUnit<'tcx>>,
- 'tcx: 'a,
-{
- if cfg!(debug_assertions) {
- debug!("{}", label);
- for cgu in cgus {
- debug!("CodegenUnit {} estimated size {} :", cgu.name(), cgu.size_estimate());
-
- for (mono_item, linkage) in cgu.items() {
- let symbol_name = mono_item.symbol_name(tcx).name;
- let symbol_hash_start = symbol_name.rfind('h');
- let symbol_hash =
- symbol_hash_start.map(|i| &symbol_name[i..]).unwrap_or("<no hash>");
-
- debug!(
- " - {} [{:?}] [{}] estimated size {}",
- mono_item.to_string(tcx, true),
- linkage,
- symbol_hash,
- mono_item.size_estimate(tcx)
- );
- }
-
- debug!("");
- }
- }
-}
-
-#[inline(never)] // give this a place in the profiler
-fn assert_symbols_are_distinct<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I)
-where
- I: Iterator<Item = &'a MonoItem<'tcx>>,
- 'tcx: 'a,
-{
- let _prof_timer = tcx.prof.generic_activity("assert_symbols_are_distinct");
-
- let mut symbols: Vec<_> =
- mono_items.map(|mono_item| (mono_item, mono_item.symbol_name(tcx))).collect();
-
- symbols.sort_by_key(|sym| sym.1);
-
- for pair in symbols.windows(2) {
- let sym1 = &pair[0].1;
- let sym2 = &pair[1].1;
-
- if sym1 == sym2 {
- let mono_item1 = pair[0].0;
- let mono_item2 = pair[1].0;
-
- let span1 = mono_item1.local_span(tcx);
- let span2 = mono_item2.local_span(tcx);
-
- // Deterministically select one of the spans for error reporting
- let span = match (span1, span2) {
- (Some(span1), Some(span2)) => {
- Some(if span1.lo().0 > span2.lo().0 { span1 } else { span2 })
- }
- (span1, span2) => span1.or(span2),
- };
-
- let error_message = format!("symbol `{}` is already defined", sym1);
-
- if let Some(span) = span {
- tcx.sess.span_fatal(span, &error_message)
- } else {
- tcx.sess.fatal(&error_message)
- }
- }
- }
-}
-
-fn collect_and_partition_mono_items(
- tcx: TyCtxt<'tcx>,
- cnum: CrateNum,
-) -> (&'tcx DefIdSet, &'tcx [CodegenUnit<'tcx>]) {
- assert_eq!(cnum, LOCAL_CRATE);
-
- let collection_mode = match tcx.sess.opts.debugging_opts.print_mono_items {
- Some(ref s) => {
- let mode_string = s.to_lowercase();
- let mode_string = mode_string.trim();
- if mode_string == "eager" {
- MonoItemCollectionMode::Eager
- } else {
- if mode_string != "lazy" {
- let message = format!(
- "Unknown codegen-item collection mode '{}'. \
- Falling back to 'lazy' mode.",
- mode_string
- );
- tcx.sess.warn(&message);
- }
-
- MonoItemCollectionMode::Lazy
- }
- }
- None => {
- if tcx.sess.opts.cg.link_dead_code == Some(true) {
- MonoItemCollectionMode::Eager
- } else {
- MonoItemCollectionMode::Lazy
- }
- }
- };
-
- let (items, inlining_map) = collector::collect_crate_mono_items(tcx, collection_mode);
-
- tcx.sess.abort_if_errors();
-
- let (codegen_units, _) = tcx.sess.time("partition_and_assert_distinct_symbols", || {
- sync::join(
- || {
- &*tcx.arena.alloc_from_iter(partition(
- tcx,
- items.iter().cloned(),
- tcx.sess.codegen_units(),
- &inlining_map,
- ))
- },
- || assert_symbols_are_distinct(tcx, items.iter()),
- )
- });
-
- let mono_items: DefIdSet = items
- .iter()
- .filter_map(|mono_item| match *mono_item {
- MonoItem::Fn(ref instance) => Some(instance.def_id()),
- MonoItem::Static(def_id) => Some(def_id),
- _ => None,
- })
- .collect();
-
- if tcx.sess.opts.debugging_opts.print_mono_items.is_some() {
- let mut item_to_cgus: FxHashMap<_, Vec<_>> = Default::default();
-
- for cgu in codegen_units {
- for (&mono_item, &linkage) in cgu.items() {
- item_to_cgus.entry(mono_item).or_default().push((cgu.name(), linkage));
- }
- }
-
- let mut item_keys: Vec<_> = items
- .iter()
- .map(|i| {
- let mut output = i.to_string(tcx, false);
- output.push_str(" @@");
- let mut empty = Vec::new();
- let cgus = item_to_cgus.get_mut(i).unwrap_or(&mut empty);
- cgus.sort_by_key(|(name, _)| *name);
- cgus.dedup();
- for &(ref cgu_name, (linkage, _)) in cgus.iter() {
- output.push_str(" ");
- output.push_str(&cgu_name.as_str());
-
- let linkage_abbrev = match linkage {
- Linkage::External => "External",
- Linkage::AvailableExternally => "Available",
- Linkage::LinkOnceAny => "OnceAny",
- Linkage::LinkOnceODR => "OnceODR",
- Linkage::WeakAny => "WeakAny",
- Linkage::WeakODR => "WeakODR",
- Linkage::Appending => "Appending",
- Linkage::Internal => "Internal",
- Linkage::Private => "Private",
- Linkage::ExternalWeak => "ExternalWeak",
- Linkage::Common => "Common",
- };
-
- output.push_str("[");
- output.push_str(linkage_abbrev);
- output.push_str("]");
- }
- output
- })
- .collect();
-
- item_keys.sort();
-
- for item in item_keys {
- println!("MONO_ITEM {}", item);
- }
- }
-
- (tcx.arena.alloc(mono_items), codegen_units)
-}
-
-pub fn provide(providers: &mut Providers) {
- providers.collect_and_partition_mono_items = collect_and_partition_mono_items;
-
- providers.is_codegened_item = |tcx, def_id| {
- let (all_mono_items, _) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
- all_mono_items.contains(&def_id)
- };
-
- providers.codegen_unit = |tcx, name| {
- let (_, all) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
- all.iter()
- .find(|cgu| cgu.name() == name)
- .unwrap_or_else(|| panic!("failed to find cgu with name {:?}", name))
- };
-}
--- /dev/null
+use std::collections::hash_map::Entry;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
+use rustc_middle::middle::exported_symbols::SymbolExportLevel;
+use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, Linkage, Visibility};
+use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
+use rustc_middle::ty::print::characteristic_def_id_of_type;
+use rustc_middle::ty::{self, DefIdTree, InstanceDef, TyCtxt};
+use rustc_span::symbol::Symbol;
+
+use crate::monomorphize::collector::InliningMap;
+use crate::monomorphize::partitioning::merging;
+use crate::monomorphize::partitioning::{
+ MonoItemPlacement, Partitioner, PostInliningPartitioning, PreInliningPartitioning,
+};
+
+pub struct DefaultPartitioning;
+
+impl<'tcx> Partitioner<'tcx> for DefaultPartitioning {
+ fn place_root_mono_items(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ mono_items: &mut dyn Iterator<Item = MonoItem<'tcx>>,
+ ) -> PreInliningPartitioning<'tcx> {
+ let mut roots = FxHashSet::default();
+ let mut codegen_units = FxHashMap::default();
+ let is_incremental_build = tcx.sess.opts.incremental.is_some();
+ let mut internalization_candidates = FxHashSet::default();
+
+ // Determine if monomorphizations instantiated in this crate will be made
+ // available to downstream crates. This depends on whether we are in
+ // share-generics mode and whether the current crate can even have
+ // downstream crates.
+ let export_generics = tcx.sess.opts.share_generics() && tcx.local_crate_exports_generics();
+
+ let cgu_name_builder = &mut CodegenUnitNameBuilder::new(tcx);
+ let cgu_name_cache = &mut FxHashMap::default();
+
+ for mono_item in mono_items {
+ match mono_item.instantiation_mode(tcx) {
+ InstantiationMode::GloballyShared { .. } => {}
+ InstantiationMode::LocalCopy => continue,
+ }
+
+ let characteristic_def_id = characteristic_def_id_of_mono_item(tcx, mono_item);
+ let is_volatile = is_incremental_build && mono_item.is_generic_fn();
+
+ let codegen_unit_name = match characteristic_def_id {
+ Some(def_id) => compute_codegen_unit_name(
+ tcx,
+ cgu_name_builder,
+ def_id,
+ is_volatile,
+ cgu_name_cache,
+ ),
+ None => fallback_cgu_name(cgu_name_builder),
+ };
+
+ let codegen_unit = codegen_units
+ .entry(codegen_unit_name)
+ .or_insert_with(|| CodegenUnit::new(codegen_unit_name));
+
+ let mut can_be_internalized = true;
+ let (linkage, visibility) = mono_item_linkage_and_visibility(
+ tcx,
+ &mono_item,
+ &mut can_be_internalized,
+ export_generics,
+ );
+ if visibility == Visibility::Hidden && can_be_internalized {
+ internalization_candidates.insert(mono_item);
+ }
+
+ codegen_unit.items_mut().insert(mono_item, (linkage, visibility));
+ roots.insert(mono_item);
+ }
+
+ // Always ensure we have at least one CGU; otherwise, if we have a
+ // crate with just types (for example), we could wind up with no CGU.
+ if codegen_units.is_empty() {
+ let codegen_unit_name = fallback_cgu_name(cgu_name_builder);
+ codegen_units.insert(codegen_unit_name, CodegenUnit::new(codegen_unit_name));
+ }
+
+ PreInliningPartitioning {
+ codegen_units: codegen_units
+ .into_iter()
+ .map(|(_, codegen_unit)| codegen_unit)
+ .collect(),
+ roots,
+ internalization_candidates,
+ }
+ }
+
+ fn merge_codegen_units(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ initial_partitioning: &mut PreInliningPartitioning<'tcx>,
+ target_cgu_count: usize,
+ ) {
+ merging::merge_codegen_units(tcx, initial_partitioning, target_cgu_count);
+ }
+
+ fn place_inlined_mono_items(
+ &mut self,
+ initial_partitioning: PreInliningPartitioning<'tcx>,
+ inlining_map: &InliningMap<'tcx>,
+ ) -> PostInliningPartitioning<'tcx> {
+ let mut new_partitioning = Vec::new();
+ let mut mono_item_placements = FxHashMap::default();
+
+ let PreInliningPartitioning {
+ codegen_units: initial_cgus,
+ roots,
+ internalization_candidates,
+ } = initial_partitioning;
+
+ let single_codegen_unit = initial_cgus.len() == 1;
+
+ for old_codegen_unit in initial_cgus {
+ // Collect all items that need to be available in this codegen unit.
+ let mut reachable = FxHashSet::default();
+ for root in old_codegen_unit.items().keys() {
+ follow_inlining(*root, inlining_map, &mut reachable);
+ }
+
+ let mut new_codegen_unit = CodegenUnit::new(old_codegen_unit.name());
+
+ // Add all monomorphizations that are not already there.
+ for mono_item in reachable {
+ if let Some(linkage) = old_codegen_unit.items().get(&mono_item) {
+ // This is a root, just copy it over.
+ new_codegen_unit.items_mut().insert(mono_item, *linkage);
+ } else {
+ if roots.contains(&mono_item) {
+ bug!(
+ "GloballyShared mono-item inlined into other CGU: \
+ {:?}",
+ mono_item
+ );
+ }
+
+ // This is a CGU-private copy.
+ new_codegen_unit
+ .items_mut()
+ .insert(mono_item, (Linkage::Internal, Visibility::Default));
+ }
+
+ if !single_codegen_unit {
+ // If there is more than one codegen unit, we need to keep track
+ // in which codegen units each monomorphization is placed.
+ match mono_item_placements.entry(mono_item) {
+ Entry::Occupied(e) => {
+ let placement = e.into_mut();
+ debug_assert!(match *placement {
+ MonoItemPlacement::SingleCgu { cgu_name } => {
+ cgu_name != new_codegen_unit.name()
+ }
+ MonoItemPlacement::MultipleCgus => true,
+ });
+ *placement = MonoItemPlacement::MultipleCgus;
+ }
+ Entry::Vacant(e) => {
+ e.insert(MonoItemPlacement::SingleCgu {
+ cgu_name: new_codegen_unit.name(),
+ });
+ }
+ }
+ }
+ }
+
+ new_partitioning.push(new_codegen_unit);
+ }
+
+ return PostInliningPartitioning {
+ codegen_units: new_partitioning,
+ mono_item_placements,
+ internalization_candidates,
+ };
+
+ fn follow_inlining<'tcx>(
+ mono_item: MonoItem<'tcx>,
+ inlining_map: &InliningMap<'tcx>,
+ visited: &mut FxHashSet<MonoItem<'tcx>>,
+ ) {
+ if !visited.insert(mono_item) {
+ return;
+ }
+
+ inlining_map.with_inlining_candidates(mono_item, |target| {
+ follow_inlining(target, inlining_map, visited);
+ });
+ }
+ }
+
+ fn internalize_symbols(
+ &mut self,
+ _tcx: TyCtxt<'tcx>,
+ partitioning: &mut PostInliningPartitioning<'tcx>,
+ inlining_map: &InliningMap<'tcx>,
+ ) {
+ if partitioning.codegen_units.len() == 1 {
+ // Fast path for when there is only one codegen unit. In this case we
+ // can internalize all candidates, since there is nowhere else they
+ // could be accessed from.
+ for cgu in &mut partitioning.codegen_units {
+ for candidate in &partitioning.internalization_candidates {
+ cgu.items_mut().insert(*candidate, (Linkage::Internal, Visibility::Default));
+ }
+ }
+
+ return;
+ }
+
+ // Build a map from every monomorphization to all the monomorphizations that
+ // reference it.
+ let mut accessor_map: FxHashMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>> = Default::default();
+ inlining_map.iter_accesses(|accessor, accessees| {
+ for accessee in accessees {
+ accessor_map.entry(*accessee).or_default().push(accessor);
+ }
+ });
+
+ let mono_item_placements = &partitioning.mono_item_placements;
+
+ // For each internalization candidates in each codegen unit, check if it is
+ // accessed from outside its defining codegen unit.
+ for cgu in &mut partitioning.codegen_units {
+ let home_cgu = MonoItemPlacement::SingleCgu { cgu_name: cgu.name() };
+
+ for (accessee, linkage_and_visibility) in cgu.items_mut() {
+ if !partitioning.internalization_candidates.contains(accessee) {
+ // This item is no candidate for internalizing, so skip it.
+ continue;
+ }
+ debug_assert_eq!(mono_item_placements[accessee], home_cgu);
+
+ if let Some(accessors) = accessor_map.get(accessee) {
+ if accessors
+ .iter()
+ .filter_map(|accessor| {
+ // Some accessors might not have been
+ // instantiated. We can safely ignore those.
+ mono_item_placements.get(accessor)
+ })
+ .any(|placement| *placement != home_cgu)
+ {
+ // Found an accessor from another CGU, so skip to the next
+ // item without marking this one as internal.
+ continue;
+ }
+ }
+
+ // If we got here, we did not find any accesses from other CGUs,
+ // so it's fine to make this monomorphization internal.
+ *linkage_and_visibility = (Linkage::Internal, Visibility::Default);
+ }
+ }
+ }
+}
+
+fn characteristic_def_id_of_mono_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ mono_item: MonoItem<'tcx>,
+) -> Option<DefId> {
+ match mono_item {
+ MonoItem::Fn(instance) => {
+ let def_id = match instance.def {
+ ty::InstanceDef::Item(def) => def.did,
+ ty::InstanceDef::VtableShim(..)
+ | ty::InstanceDef::ReifyShim(..)
+ | ty::InstanceDef::FnPtrShim(..)
+ | ty::InstanceDef::ClosureOnceShim { .. }
+ | ty::InstanceDef::Intrinsic(..)
+ | ty::InstanceDef::DropGlue(..)
+ | ty::InstanceDef::Virtual(..)
+ | ty::InstanceDef::CloneShim(..) => return None,
+ };
+
+ // If this is a method, we want to put it into the same module as
+ // its self-type. If the self-type does not provide a characteristic
+ // DefId, we use the location of the impl after all.
+
+ if tcx.trait_of_item(def_id).is_some() {
+ let self_ty = instance.substs.type_at(0);
+ // This is a default implementation of a trait method.
+ return characteristic_def_id_of_type(self_ty).or(Some(def_id));
+ }
+
+ if let Some(impl_def_id) = tcx.impl_of_method(def_id) {
+ if tcx.sess.opts.incremental.is_some()
+ && tcx.trait_id_of_impl(impl_def_id) == tcx.lang_items().drop_trait()
+ {
+ // Put `Drop::drop` into the same cgu as `drop_in_place`
+ // since `drop_in_place` is the only thing that can
+ // call it.
+ return None;
+ }
+ // This is a method within an impl, find out what the self-type is:
+ let impl_self_ty = tcx.subst_and_normalize_erasing_regions(
+ instance.substs,
+ ty::ParamEnv::reveal_all(),
+ &tcx.type_of(impl_def_id),
+ );
+ if let Some(def_id) = characteristic_def_id_of_type(impl_self_ty) {
+ return Some(def_id);
+ }
+ }
+
+ Some(def_id)
+ }
+ MonoItem::Static(def_id) => Some(def_id),
+ MonoItem::GlobalAsm(hir_id) => Some(tcx.hir().local_def_id(hir_id).to_def_id()),
+ }
+}
+
+fn compute_codegen_unit_name(
+ tcx: TyCtxt<'_>,
+ name_builder: &mut CodegenUnitNameBuilder<'_>,
+ def_id: DefId,
+ volatile: bool,
+ cache: &mut CguNameCache,
+) -> Symbol {
+ // Find the innermost module that is not nested within a function.
+ let mut current_def_id = def_id;
+ let mut cgu_def_id = None;
+ // Walk backwards from the item we want to find the module for.
+ loop {
+ if current_def_id.index == CRATE_DEF_INDEX {
+ if cgu_def_id.is_none() {
+ // If we have not found a module yet, take the crate root.
+ cgu_def_id = Some(DefId { krate: def_id.krate, index: CRATE_DEF_INDEX });
+ }
+ break;
+ } else if tcx.def_kind(current_def_id) == DefKind::Mod {
+ if cgu_def_id.is_none() {
+ cgu_def_id = Some(current_def_id);
+ }
+ } else {
+ // If we encounter something that is not a module, throw away
+ // any module that we've found so far because we now know that
+ // it is nested within something else.
+ cgu_def_id = None;
+ }
+
+ current_def_id = tcx.parent(current_def_id).unwrap();
+ }
+
+ let cgu_def_id = cgu_def_id.unwrap();
+
+ *cache.entry((cgu_def_id, volatile)).or_insert_with(|| {
+ let def_path = tcx.def_path(cgu_def_id);
+
+ let components = def_path.data.iter().map(|part| part.data.as_symbol());
+
+ let volatile_suffix = volatile.then_some("volatile");
+
+ name_builder.build_cgu_name(def_path.krate, components, volatile_suffix)
+ })
+}
+
+// Anything we can't find a proper codegen unit for goes into this.
+fn fallback_cgu_name(name_builder: &mut CodegenUnitNameBuilder<'_>) -> Symbol {
+ name_builder.build_cgu_name(LOCAL_CRATE, &["fallback"], Some("cgu"))
+}
+
+fn mono_item_linkage_and_visibility(
+ tcx: TyCtxt<'tcx>,
+ mono_item: &MonoItem<'tcx>,
+ can_be_internalized: &mut bool,
+ export_generics: bool,
+) -> (Linkage, Visibility) {
+ if let Some(explicit_linkage) = mono_item.explicit_linkage(tcx) {
+ return (explicit_linkage, Visibility::Default);
+ }
+ let vis = mono_item_visibility(tcx, mono_item, can_be_internalized, export_generics);
+ (Linkage::External, vis)
+}
+
+type CguNameCache = FxHashMap<(DefId, bool), Symbol>;
+
+fn mono_item_visibility(
+ tcx: TyCtxt<'tcx>,
+ mono_item: &MonoItem<'tcx>,
+ can_be_internalized: &mut bool,
+ export_generics: bool,
+) -> Visibility {
+ let instance = match mono_item {
+ // This is pretty complicated; see below.
+ MonoItem::Fn(instance) => instance,
+
+ // Misc handling for generics and such, but otherwise:
+ MonoItem::Static(def_id) => {
+ return if tcx.is_reachable_non_generic(*def_id) {
+ *can_be_internalized = false;
+ default_visibility(tcx, *def_id, false)
+ } else {
+ Visibility::Hidden
+ };
+ }
+ MonoItem::GlobalAsm(hir_id) => {
+ let def_id = tcx.hir().local_def_id(*hir_id);
+ return if tcx.is_reachable_non_generic(def_id) {
+ *can_be_internalized = false;
+ default_visibility(tcx, def_id.to_def_id(), false)
+ } else {
+ Visibility::Hidden
+ };
+ }
+ };
+
+ let def_id = match instance.def {
+ InstanceDef::Item(def) => def.did,
+ InstanceDef::DropGlue(def_id, Some(_)) => def_id,
+
+ // These are all compiler glue and such, never exported, always hidden.
+ InstanceDef::VtableShim(..)
+ | InstanceDef::ReifyShim(..)
+ | InstanceDef::FnPtrShim(..)
+ | InstanceDef::Virtual(..)
+ | InstanceDef::Intrinsic(..)
+ | InstanceDef::ClosureOnceShim { .. }
+ | InstanceDef::DropGlue(..)
+ | InstanceDef::CloneShim(..) => return Visibility::Hidden,
+ };
+
+ // The `start_fn` lang item is actually a monomorphized instance of a
+ // function in the standard library, used for the `main` function. We don't
+ // want to export it so we tag it with `Hidden` visibility but this symbol
+ // is only referenced from the actual `main` symbol which we unfortunately
+ // don't know anything about during partitioning/collection. As a result we
+ // forcibly keep this symbol out of the `internalization_candidates` set.
+ //
+ // FIXME: eventually we don't want to always force this symbol to have
+ // hidden visibility, it should indeed be a candidate for
+ // internalization, but we have to understand that it's referenced
+ // from the `main` symbol we'll generate later.
+ //
+ // This may be fixable with a new `InstanceDef` perhaps? Unsure!
+ if tcx.lang_items().start_fn() == Some(def_id) {
+ *can_be_internalized = false;
+ return Visibility::Hidden;
+ }
+
+ let is_generic = instance.substs.non_erasable_generics().next().is_some();
+
+ // Upstream `DefId` instances get different handling than local ones.
+ if !def_id.is_local() {
+ return if export_generics && is_generic {
+ // If it is a upstream monomorphization and we export generics, we must make
+ // it available to downstream crates.
+ *can_be_internalized = false;
+ default_visibility(tcx, def_id, true)
+ } else {
+ Visibility::Hidden
+ };
+ }
+
+ if is_generic {
+ if export_generics {
+ if tcx.is_unreachable_local_definition(def_id) {
+ // This instance cannot be used from another crate.
+ Visibility::Hidden
+ } else {
+ // This instance might be useful in a downstream crate.
+ *can_be_internalized = false;
+ default_visibility(tcx, def_id, true)
+ }
+ } else {
+ // We are not exporting generics or the definition is not reachable
+ // for downstream crates, we can internalize its instantiations.
+ Visibility::Hidden
+ }
+ } else {
+ // If this isn't a generic function then we mark this a `Default` if
+ // this is a reachable item, meaning that it's a symbol other crates may
+ // access when they link to us.
+ if tcx.is_reachable_non_generic(def_id) {
+ *can_be_internalized = false;
+ debug_assert!(!is_generic);
+ return default_visibility(tcx, def_id, false);
+ }
+
+ // If this isn't reachable then we're gonna tag this with `Hidden`
+ // visibility. In some situations though we'll want to prevent this
+ // symbol from being internalized.
+ //
+ // There's two categories of items here:
+ //
+ // * First is weak lang items. These are basically mechanisms for
+ // libcore to forward-reference symbols defined later in crates like
+ // the standard library or `#[panic_handler]` definitions. The
+ // definition of these weak lang items needs to be referenceable by
+ // libcore, so we're no longer a candidate for internalization.
+ // Removal of these functions can't be done by LLVM but rather must be
+ // done by the linker as it's a non-local decision.
+ //
+ // * Second is "std internal symbols". Currently this is primarily used
+ // for allocator symbols. Allocators are a little weird in their
+ // implementation, but the idea is that the compiler, at the last
+ // minute, defines an allocator with an injected object file. The
+ // `alloc` crate references these symbols (`__rust_alloc`) and the
+ // definition doesn't get hooked up until a linked crate artifact is
+ // generated.
+ //
+ // The symbols synthesized by the compiler (`__rust_alloc`) are thin
+ // veneers around the actual implementation, some other symbol which
+ // implements the same ABI. These symbols (things like `__rg_alloc`,
+ // `__rdl_alloc`, `__rde_alloc`, etc), are all tagged with "std
+ // internal symbols".
+ //
+ // The std-internal symbols here **should not show up in a dll as an
+ // exported interface**, so they return `false` from
+ // `is_reachable_non_generic` above and we'll give them `Hidden`
+ // visibility below. Like the weak lang items, though, we can't let
+ // LLVM internalize them as this decision is left up to the linker to
+ // omit them, so prevent them from being internalized.
+ let attrs = tcx.codegen_fn_attrs(def_id);
+ if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) {
+ *can_be_internalized = false;
+ }
+
+ Visibility::Hidden
+ }
+}
+
+fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibility {
+ if !tcx.sess.target.target.options.default_hidden_visibility {
+ return Visibility::Default;
+ }
+
+ // Generic functions never have export-level C.
+ if is_generic {
+ return Visibility::Hidden;
+ }
+
+ // Things with export level C don't get instantiated in
+ // downstream crates.
+ if !id.is_local() {
+ return Visibility::Hidden;
+ }
+
+ // C-export level items remain at `Default`, all other internal
+ // items become `Hidden`.
+ match tcx.reachable_non_generics(id.krate).get(&id) {
+ Some(SymbolExportLevel::C) => Visibility::Default,
+ _ => Visibility::Hidden,
+ }
+}
--- /dev/null
+use std::cmp;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder};
+use rustc_middle::ty::TyCtxt;
+use rustc_span::symbol::{Symbol, SymbolStr};
+
+use crate::monomorphize::partitioning::PreInliningPartitioning;
+
+pub fn merge_codegen_units<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ initial_partitioning: &mut PreInliningPartitioning<'tcx>,
+ target_cgu_count: usize,
+) {
+ assert!(target_cgu_count >= 1);
+ let codegen_units = &mut initial_partitioning.codegen_units;
+
+ // Note that at this point in time the `codegen_units` here may not be in a
+ // deterministic order (but we know they're deterministically the same set).
+ // We want this merging to produce a deterministic ordering of codegen units
+ // from the input.
+ //
+ // Due to basically how we've implemented the merging below (merge the two
+ // smallest into each other) we're sure to start off with a deterministic
+ // order (sorted by name). This'll mean that if two cgus have the same size
+ // the stable sort below will keep everything nice and deterministic.
+ codegen_units.sort_by_cached_key(|cgu| cgu.name().as_str());
+
+ // This map keeps track of what got merged into what.
+ let mut cgu_contents: FxHashMap<Symbol, Vec<SymbolStr>> =
+ codegen_units.iter().map(|cgu| (cgu.name(), vec![cgu.name().as_str()])).collect();
+
+ // Merge the two smallest codegen units until the target size is reached.
+ while codegen_units.len() > target_cgu_count {
+ // Sort small cgus to the back
+ codegen_units.sort_by_cached_key(|cgu| cmp::Reverse(cgu.size_estimate()));
+ let mut smallest = codegen_units.pop().unwrap();
+ let second_smallest = codegen_units.last_mut().unwrap();
+
+ // Move the mono-items from `smallest` to `second_smallest`
+ second_smallest.modify_size_estimate(smallest.size_estimate());
+ for (k, v) in smallest.items_mut().drain() {
+ second_smallest.items_mut().insert(k, v);
+ }
+
+ // Record that `second_smallest` now contains all the stuff that was in
+ // `smallest` before.
+ let mut consumed_cgu_names = cgu_contents.remove(&smallest.name()).unwrap();
+ cgu_contents.get_mut(&second_smallest.name()).unwrap().extend(consumed_cgu_names.drain(..));
+
+ debug!(
+ "CodegenUnit {} merged into CodegenUnit {}",
+ smallest.name(),
+ second_smallest.name()
+ );
+ }
+
+ let cgu_name_builder = &mut CodegenUnitNameBuilder::new(tcx);
+
+ if tcx.sess.opts.incremental.is_some() {
+ // If we are doing incremental compilation, we want CGU names to
+ // reflect the path of the source level module they correspond to.
+ // For CGUs that contain the code of multiple modules because of the
+ // merging done above, we use a concatenation of the names of
+ // all contained CGUs.
+ let new_cgu_names: FxHashMap<Symbol, String> = cgu_contents
+ .into_iter()
+ // This `filter` makes sure we only update the name of CGUs that
+ // were actually modified by merging.
+ .filter(|(_, cgu_contents)| cgu_contents.len() > 1)
+ .map(|(current_cgu_name, cgu_contents)| {
+ let mut cgu_contents: Vec<&str> = cgu_contents.iter().map(|s| &s[..]).collect();
+
+ // Sort the names, so things are deterministic and easy to
+ // predict.
+ cgu_contents.sort();
+
+ (current_cgu_name, cgu_contents.join("--"))
+ })
+ .collect();
+
+ for cgu in codegen_units.iter_mut() {
+ if let Some(new_cgu_name) = new_cgu_names.get(&cgu.name()) {
+ if tcx.sess.opts.debugging_opts.human_readable_cgu_names {
+ cgu.set_name(Symbol::intern(&new_cgu_name));
+ } else {
+ // If we don't require CGU names to be human-readable, we
+ // use a fixed length hash of the composite CGU name
+ // instead.
+ let new_cgu_name = CodegenUnit::mangle_name(&new_cgu_name);
+ cgu.set_name(Symbol::intern(&new_cgu_name));
+ }
+ }
+ }
+ } else {
+ // If we are compiling non-incrementally we just generate simple CGU
+ // names containing an index.
+ for (index, cgu) in codegen_units.iter_mut().enumerate() {
+ cgu.set_name(numbered_codegen_unit_name(cgu_name_builder, index));
+ }
+ }
+}
+
+fn numbered_codegen_unit_name(
+ name_builder: &mut CodegenUnitNameBuilder<'_>,
+ index: usize,
+) -> Symbol {
+ name_builder.build_cgu_name_no_mangle(LOCAL_CRATE, &["cgu"], Some(index))
+}
--- /dev/null
+//! Partitioning Codegen Units for Incremental Compilation
+//! ======================================================
+//!
+//! The task of this module is to take the complete set of monomorphizations of
+//! a crate and produce a set of codegen units from it, where a codegen unit
+//! is a named set of (mono-item, linkage) pairs. That is, this module
+//! decides which monomorphization appears in which codegen units with which
+//! linkage. The following paragraphs describe some of the background on the
+//! partitioning scheme.
+//!
+//! The most important opportunity for saving on compilation time with
+//! incremental compilation is to avoid re-codegenning and re-optimizing code.
+//! Since the unit of codegen and optimization for LLVM is "modules" or, how
+//! we call them "codegen units", the particulars of how much time can be saved
+//! by incremental compilation are tightly linked to how the output program is
+//! partitioned into these codegen units prior to passing it to LLVM --
+//! especially because we have to treat codegen units as opaque entities once
+//! they are created: There is no way for us to incrementally update an existing
+//! LLVM module and so we have to build any such module from scratch if it was
+//! affected by some change in the source code.
+//!
+//! From that point of view it would make sense to maximize the number of
+//! codegen units by, for example, putting each function into its own module.
+//! That way only those modules would have to be re-compiled that were actually
+//! affected by some change, minimizing the number of functions that could have
+//! been re-used but just happened to be located in a module that is
+//! re-compiled.
+//!
+//! However, since LLVM optimization does not work across module boundaries,
+//! using such a highly granular partitioning would lead to very slow runtime
+//! code since it would effectively prohibit inlining and other inter-procedure
+//! optimizations. We want to avoid that as much as possible.
+//!
+//! Thus we end up with a trade-off: The bigger the codegen units, the better
+//! LLVM's optimizer can do its work, but also the smaller the compilation time
+//! reduction we get from incremental compilation.
+//!
+//! Ideally, we would create a partitioning such that there are few big codegen
+//! units with few interdependencies between them. For now though, we use the
+//! following heuristic to determine the partitioning:
+//!
+//! - There are two codegen units for every source-level module:
+//! - One for "stable", that is non-generic, code
+//! - One for more "volatile" code, i.e., monomorphized instances of functions
+//! defined in that module
+//!
+//! In order to see why this heuristic makes sense, let's take a look at when a
+//! codegen unit can get invalidated:
+//!
+//! 1. The most straightforward case is when the BODY of a function or global
+//! changes. Then any codegen unit containing the code for that item has to be
+//! re-compiled. Note that this includes all codegen units where the function
+//! has been inlined.
+//!
+//! 2. The next case is when the SIGNATURE of a function or global changes. In
+//! this case, all codegen units containing a REFERENCE to that item have to be
+//! re-compiled. This is a superset of case 1.
+//!
+//! 3. The final and most subtle case is when a REFERENCE to a generic function
+//! is added or removed somewhere. Even though the definition of the function
+//! might be unchanged, a new REFERENCE might introduce a new monomorphized
+//! instance of this function which has to be placed and compiled somewhere.
+//! Conversely, when removing a REFERENCE, it might have been the last one with
+//! that particular set of generic arguments and thus we have to remove it.
+//!
+//! From the above we see that just using one codegen unit per source-level
+//! module is not such a good idea, since just adding a REFERENCE to some
+//! generic item somewhere else would invalidate everything within the module
+//! containing the generic item. The heuristic above reduces this detrimental
+//! side-effect of references a little by at least not touching the non-generic
+//! code of the module.
+//!
+//! A Note on Inlining
+//! ------------------
+//! As briefly mentioned above, in order for LLVM to be able to inline a
+//! function call, the body of the function has to be available in the LLVM
+//! module where the call is made. This has a few consequences for partitioning:
+//!
+//! - The partitioning algorithm has to take care of placing functions into all
+//! codegen units where they should be available for inlining. It also has to
+//! decide on the correct linkage for these functions.
+//!
+//! - The partitioning algorithm has to know which functions are likely to get
+//! inlined, so it can distribute function instantiations accordingly. Since
+//! there is no way of knowing for sure which functions LLVM will decide to
+//! inline in the end, we apply a heuristic here: Only functions marked with
+//! `#[inline]` are considered for inlining by the partitioner. The current
+//! implementation will not try to determine if a function is likely to be
+//! inlined by looking at the functions definition.
+//!
+//! Note though that as a side-effect of creating a codegen units per
+//! source-level module, functions from the same module will be available for
+//! inlining, even when they are not marked `#[inline]`.
+
+mod default;
+mod merging;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::sync;
+use rustc_hir::def_id::{CrateNum, DefIdSet, LOCAL_CRATE};
+use rustc_middle::mir::mono::MonoItem;
+use rustc_middle::mir::mono::{CodegenUnit, Linkage};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::symbol::Symbol;
+
+use crate::monomorphize::collector::InliningMap;
+use crate::monomorphize::collector::{self, MonoItemCollectionMode};
+
+trait Partitioner<'tcx> {
+ fn place_root_mono_items(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ mono_items: &mut dyn Iterator<Item = MonoItem<'tcx>>,
+ ) -> PreInliningPartitioning<'tcx>;
+
+ fn merge_codegen_units(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ initial_partitioning: &mut PreInliningPartitioning<'tcx>,
+ target_cgu_count: usize,
+ );
+
+ fn place_inlined_mono_items(
+ &mut self,
+ initial_partitioning: PreInliningPartitioning<'tcx>,
+ inlining_map: &InliningMap<'tcx>,
+ ) -> PostInliningPartitioning<'tcx>;
+
+ fn internalize_symbols(
+ &mut self,
+ tcx: TyCtxt<'tcx>,
+ partitioning: &mut PostInliningPartitioning<'tcx>,
+ inlining_map: &InliningMap<'tcx>,
+ );
+}
+
+fn get_partitioner<'tcx>(tcx: TyCtxt<'tcx>) -> Box<dyn Partitioner<'tcx>> {
+ let strategy = match &tcx.sess.opts.debugging_opts.cgu_partitioning_strategy {
+ None => "default",
+ Some(s) => &s[..],
+ };
+
+ match strategy {
+ "default" => Box::new(default::DefaultPartitioning),
+ _ => tcx.sess.fatal("unknown partitioning strategy"),
+ }
+}
+
+pub fn partition<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ mono_items: &mut dyn Iterator<Item = MonoItem<'tcx>>,
+ max_cgu_count: usize,
+ inlining_map: &InliningMap<'tcx>,
+) -> Vec<CodegenUnit<'tcx>> {
+ let _prof_timer = tcx.prof.generic_activity("cgu_partitioning");
+
+ let mut partitioner = get_partitioner(tcx);
+ // In the first step, we place all regular monomorphizations into their
+ // respective 'home' codegen unit. Regular monomorphizations are all
+ // functions and statics defined in the local crate.
+ let mut initial_partitioning = {
+ let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_roots");
+ partitioner.place_root_mono_items(tcx, mono_items)
+ };
+
+ initial_partitioning.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
+
+ debug_dump(tcx, "INITIAL PARTITIONING:", initial_partitioning.codegen_units.iter());
+
+ // Merge until we have at most `max_cgu_count` codegen units.
+ {
+ let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_merge_cgus");
+ partitioner.merge_codegen_units(tcx, &mut initial_partitioning, max_cgu_count);
+ debug_dump(tcx, "POST MERGING:", initial_partitioning.codegen_units.iter());
+ }
+
+ // In the next step, we use the inlining map to determine which additional
+ // monomorphizations have to go into each codegen unit. These additional
+ // monomorphizations can be drop-glue, functions from external crates, and
+ // local functions the definition of which is marked with `#[inline]`.
+ let mut post_inlining = {
+ let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_inline_items");
+ partitioner.place_inlined_mono_items(initial_partitioning, inlining_map)
+ };
+
+ post_inlining.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
+
+ debug_dump(tcx, "POST INLINING:", post_inlining.codegen_units.iter());
+
+ // Next we try to make as many symbols "internal" as possible, so LLVM has
+ // more freedom to optimize.
+ if tcx.sess.opts.cg.link_dead_code != Some(true) {
+ let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
+ partitioner.internalize_symbols(tcx, &mut post_inlining, inlining_map);
+ }
+
+ // Finally, sort by codegen unit name, so that we get deterministic results.
+ let PostInliningPartitioning {
+ codegen_units: mut result,
+ mono_item_placements: _,
+ internalization_candidates: _,
+ } = post_inlining;
+
+ result.sort_by_cached_key(|cgu| cgu.name().as_str());
+
+ result
+}
+
+pub struct PreInliningPartitioning<'tcx> {
+ codegen_units: Vec<CodegenUnit<'tcx>>,
+ roots: FxHashSet<MonoItem<'tcx>>,
+ internalization_candidates: FxHashSet<MonoItem<'tcx>>,
+}
+
+/// For symbol internalization, we need to know whether a symbol/mono-item is
+/// accessed from outside the codegen unit it is defined in. This type is used
+/// to keep track of that.
+#[derive(Clone, PartialEq, Eq, Debug)]
+enum MonoItemPlacement {
+ SingleCgu { cgu_name: Symbol },
+ MultipleCgus,
+}
+
+struct PostInliningPartitioning<'tcx> {
+ codegen_units: Vec<CodegenUnit<'tcx>>,
+ mono_item_placements: FxHashMap<MonoItem<'tcx>, MonoItemPlacement>,
+ internalization_candidates: FxHashSet<MonoItem<'tcx>>,
+}
+
+fn debug_dump<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, label: &str, cgus: I)
+where
+ I: Iterator<Item = &'a CodegenUnit<'tcx>>,
+ 'tcx: 'a,
+{
+ if cfg!(debug_assertions) {
+ debug!("{}", label);
+ for cgu in cgus {
+ debug!("CodegenUnit {} estimated size {} :", cgu.name(), cgu.size_estimate());
+
+ for (mono_item, linkage) in cgu.items() {
+ let symbol_name = mono_item.symbol_name(tcx).name;
+ let symbol_hash_start = symbol_name.rfind('h');
+ let symbol_hash =
+ symbol_hash_start.map(|i| &symbol_name[i..]).unwrap_or("<no hash>");
+
+ debug!(
+ " - {} [{:?}] [{}] estimated size {}",
+ mono_item.to_string(tcx, true),
+ linkage,
+ symbol_hash,
+ mono_item.size_estimate(tcx)
+ );
+ }
+
+ debug!("");
+ }
+ }
+}
+
+#[inline(never)] // give this a place in the profiler
+fn assert_symbols_are_distinct<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I)
+where
+ I: Iterator<Item = &'a MonoItem<'tcx>>,
+ 'tcx: 'a,
+{
+ let _prof_timer = tcx.prof.generic_activity("assert_symbols_are_distinct");
+
+ let mut symbols: Vec<_> =
+ mono_items.map(|mono_item| (mono_item, mono_item.symbol_name(tcx))).collect();
+
+ symbols.sort_by_key(|sym| sym.1);
+
+ for pair in symbols.windows(2) {
+ let sym1 = &pair[0].1;
+ let sym2 = &pair[1].1;
+
+ if sym1 == sym2 {
+ let mono_item1 = pair[0].0;
+ let mono_item2 = pair[1].0;
+
+ let span1 = mono_item1.local_span(tcx);
+ let span2 = mono_item2.local_span(tcx);
+
+ // Deterministically select one of the spans for error reporting
+ let span = match (span1, span2) {
+ (Some(span1), Some(span2)) => {
+ Some(if span1.lo().0 > span2.lo().0 { span1 } else { span2 })
+ }
+ (span1, span2) => span1.or(span2),
+ };
+
+ let error_message = format!("symbol `{}` is already defined", sym1);
+
+ if let Some(span) = span {
+ tcx.sess.span_fatal(span, &error_message)
+ } else {
+ tcx.sess.fatal(&error_message)
+ }
+ }
+ }
+}
+
+fn collect_and_partition_mono_items<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ cnum: CrateNum,
+) -> (&'tcx DefIdSet, &'tcx [CodegenUnit<'tcx>]) {
+ assert_eq!(cnum, LOCAL_CRATE);
+
+ let collection_mode = match tcx.sess.opts.debugging_opts.print_mono_items {
+ Some(ref s) => {
+ let mode_string = s.to_lowercase();
+ let mode_string = mode_string.trim();
+ if mode_string == "eager" {
+ MonoItemCollectionMode::Eager
+ } else {
+ if mode_string != "lazy" {
+ let message = format!(
+ "Unknown codegen-item collection mode '{}'. \
+ Falling back to 'lazy' mode.",
+ mode_string
+ );
+ tcx.sess.warn(&message);
+ }
+
+ MonoItemCollectionMode::Lazy
+ }
+ }
+ None => {
+ if tcx.sess.opts.cg.link_dead_code == Some(true) {
+ MonoItemCollectionMode::Eager
+ } else {
+ MonoItemCollectionMode::Lazy
+ }
+ }
+ };
+
+ let (items, inlining_map) = collector::collect_crate_mono_items(tcx, collection_mode);
+
+ tcx.sess.abort_if_errors();
+
+ let (codegen_units, _) = tcx.sess.time("partition_and_assert_distinct_symbols", || {
+ sync::join(
+ || {
+ &*tcx.arena.alloc_from_iter(partition(
+ tcx,
+ &mut items.iter().cloned(),
+ tcx.sess.codegen_units(),
+ &inlining_map,
+ ))
+ },
+ || assert_symbols_are_distinct(tcx, items.iter()),
+ )
+ });
+
+ let mono_items: DefIdSet = items
+ .iter()
+ .filter_map(|mono_item| match *mono_item {
+ MonoItem::Fn(ref instance) => Some(instance.def_id()),
+ MonoItem::Static(def_id) => Some(def_id),
+ _ => None,
+ })
+ .collect();
+
+ if tcx.sess.opts.debugging_opts.print_mono_items.is_some() {
+ let mut item_to_cgus: FxHashMap<_, Vec<_>> = Default::default();
+
+ for cgu in codegen_units {
+ for (&mono_item, &linkage) in cgu.items() {
+ item_to_cgus.entry(mono_item).or_default().push((cgu.name(), linkage));
+ }
+ }
+
+ let mut item_keys: Vec<_> = items
+ .iter()
+ .map(|i| {
+ let mut output = i.to_string(tcx, false);
+ output.push_str(" @@");
+ let mut empty = Vec::new();
+ let cgus = item_to_cgus.get_mut(i).unwrap_or(&mut empty);
+ cgus.sort_by_key(|(name, _)| *name);
+ cgus.dedup();
+ for &(ref cgu_name, (linkage, _)) in cgus.iter() {
+ output.push_str(" ");
+ output.push_str(&cgu_name.as_str());
+
+ let linkage_abbrev = match linkage {
+ Linkage::External => "External",
+ Linkage::AvailableExternally => "Available",
+ Linkage::LinkOnceAny => "OnceAny",
+ Linkage::LinkOnceODR => "OnceODR",
+ Linkage::WeakAny => "WeakAny",
+ Linkage::WeakODR => "WeakODR",
+ Linkage::Appending => "Appending",
+ Linkage::Internal => "Internal",
+ Linkage::Private => "Private",
+ Linkage::ExternalWeak => "ExternalWeak",
+ Linkage::Common => "Common",
+ };
+
+ output.push_str("[");
+ output.push_str(linkage_abbrev);
+ output.push_str("]");
+ }
+ output
+ })
+ .collect();
+
+ item_keys.sort();
+
+ for item in item_keys {
+ println!("MONO_ITEM {}", item);
+ }
+ }
+
+ (tcx.arena.alloc(mono_items), codegen_units)
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.collect_and_partition_mono_items = collect_and_partition_mono_items;
+
+ providers.is_codegened_item = |tcx, def_id| {
+ let (all_mono_items, _) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
+ all_mono_items.contains(&def_id)
+ };
+
+ providers.codegen_unit = |tcx, name| {
+ let (_, all) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
+ all.iter()
+ .find(|cgu| cgu.name() == name)
+ .unwrap_or_else(|| panic!("failed to find cgu with name {:?}", name))
+ };
+}
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items::FnMutTraitLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::mir::*;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::subst::{InternalSubsts, Subst};
build_call_shim(tcx, instance, None, CallKind::Direct(def_id), None)
}
ty::InstanceDef::ClosureOnceShim { call_once: _ } => {
- let fn_mut = tcx.require_lang_item(FnMutTraitLangItem, None);
+ let fn_mut = tcx.require_lang_item(LangItem::FnMut, None);
let call_mut = tcx
.associated_items(fn_mut)
.in_definition_order()
fn downcast_subpath(&self, _path: Self::Path, _variant: VariantIdx) -> Option<Self::Path> {
Some(())
}
- fn array_subpath(&self, _path: Self::Path, _index: u32, _size: u32) -> Option<Self::Path> {
+ fn array_subpath(&self, _path: Self::Path, _index: u64, _size: u64) -> Option<Self::Path> {
None
}
}
//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
use rustc_errors::struct_span_err;
-use rustc_hir::{self as hir, lang_items};
+use rustc_hir::{self as hir, LangItem};
use rustc_hir::{def_id::DefId, HirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
tcx.infer_ctxt().enter(|infcx| {
let cause = traits::ObligationCause::new(body.span, hir_id, traits::SharedStatic);
let mut fulfillment_cx = traits::FulfillmentContext::new();
- let sync_def_id = tcx.require_lang_item(lang_items::SyncTraitLangItem, Some(body.span));
+ let sync_def_id = tcx.require_lang_item(LangItem::Sync, Some(body.span));
fulfillment_cx.register_bound(&infcx, ty::ParamEnv::empty(), ty, sync_def_id, cause);
if let Err(err) = fulfillment_cx.select_all_or_error(&infcx) {
infcx.report_fulfillment_errors(&err, None, false);
})
}
- fn array_subpath(&self, path: Self::Path, index: u32, size: u32) -> Option<Self::Path> {
+ fn array_subpath(&self, path: Self::Path, index: u64, size: u64) -> Option<Self::Path> {
dataflow::move_path_children_matching(self.ctxt.move_data(), path, |e| match e {
ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
debug_assert!(size == min_length, "min_length should be exact for arrays");
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items::{GeneratorStateLangItem, PinTypeLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_index::bit_set::{BitMatrix, BitSet};
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
fn make_generator_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let ref_gen_ty = body.local_decls.raw[1].ty;
- let pin_did = tcx.require_lang_item(PinTypeLangItem, Some(body.span));
+ let pin_did = tcx.require_lang_item(LangItem::Pin, Some(body.span));
let pin_adt_ref = tcx.adt_def(pin_did);
let substs = tcx.intern_substs(&[ref_gen_ty.into()]);
let pin_ref_gen_ty = tcx.mk_adt(pin_adt_ref, substs);
};
// Compute GeneratorState<yield_ty, return_ty>
- let state_did = tcx.require_lang_item(GeneratorStateLangItem, None);
+ let state_did = tcx.require_lang_item(LangItem::GeneratorState, None);
let state_adt_ref = tcx.adt_def(state_did);
let state_substs = tcx.intern_substs(&[yield_ty.into(), body.return_ty().into()]);
let ret_ty = tcx.mk_adt(state_adt_ref, state_substs);
pub mod rustc_peek;
pub mod simplify;
pub mod simplify_branches;
+pub mod simplify_comparison_integral;
pub mod simplify_try;
pub mod uninhabited_enum_branching;
pub mod unreachable_prop;
&match_branches::MatchBranchSimplification,
&const_prop::ConstProp,
&simplify_branches::SimplifyBranches::new("after-const-prop"),
+ &simplify_comparison_integral::SimplifyComparisonIntegral,
&simplify_try::SimplifyArmIdentity,
&simplify_try::SimplifyBranchSame,
©_prop::CopyPropagation,
--- /dev/null
+use super::{MirPass, MirSource};
+use rustc_middle::{
+ mir::{
+ interpret::Scalar, BasicBlock, BinOp, Body, Operand, Place, Rvalue, Statement,
+ StatementKind, TerminatorKind,
+ },
+ ty::{Ty, TyCtxt},
+};
+
+/// Pass to convert `if` conditions on integrals into switches on the integral.
+/// For an example, it turns something like
+///
+/// ```
+/// _3 = Eq(move _4, const 43i32);
+/// StorageDead(_4);
+/// switchInt(_3) -> [false: bb2, otherwise: bb3];
+/// ```
+///
+/// into:
+///
+/// ```
+/// switchInt(_4) -> [43i32: bb3, otherwise: bb2];
+/// ```
+pub struct SimplifyComparisonIntegral;
+
+impl<'tcx> MirPass<'tcx> for SimplifyComparisonIntegral {
+ fn run_pass(&self, _: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
+ trace!("Running SimplifyComparisonIntegral on {:?}", source);
+
+ let helper = OptimizationFinder { body };
+ let opts = helper.find_optimizations();
+ let mut storage_deads_to_insert = vec![];
+ let mut storage_deads_to_remove: Vec<(usize, BasicBlock)> = vec![];
+ for opt in opts {
+ trace!("SUCCESS: Applying {:?}", opt);
+ // replace terminator with a switchInt that switches on the integer directly
+ let bbs = &mut body.basic_blocks_mut();
+ let bb = &mut bbs[opt.bb_idx];
+ // We only use the bits for the untyped, not length checked `values` field. Thus we are
+ // not using any of the convenience wrappers here and directly access the bits.
+ let new_value = match opt.branch_value_scalar {
+ Scalar::Raw { data, .. } => data,
+ Scalar::Ptr(_) => continue,
+ };
+ const FALSE: u128 = 0;
+ let mut new_targets = opt.targets.clone();
+ let first_is_false_target = opt.values[0] == FALSE;
+ match opt.op {
+ BinOp::Eq => {
+ // if the assignment was Eq we want the true case to be first
+ if first_is_false_target {
+ new_targets.swap(0, 1);
+ }
+ }
+ BinOp::Ne => {
+ // if the assignment was Ne we want the false case to be first
+ if !first_is_false_target {
+ new_targets.swap(0, 1);
+ }
+ }
+ _ => unreachable!(),
+ }
+
+ let terminator = bb.terminator_mut();
+
+ // add StorageDead for the place switched on at the top of each target
+ for bb_idx in new_targets.iter() {
+ storage_deads_to_insert.push((
+ *bb_idx,
+ Statement {
+ source_info: terminator.source_info,
+ kind: StatementKind::StorageDead(opt.to_switch_on.local),
+ },
+ ));
+ }
+
+ terminator.kind = TerminatorKind::SwitchInt {
+ discr: Operand::Move(opt.to_switch_on),
+ switch_ty: opt.branch_value_ty,
+ values: vec![new_value].into(),
+ targets: new_targets,
+ };
+
+ // delete comparison statement if it the value being switched on was moved, which means it can not be user later on
+ if opt.can_remove_bin_op_stmt {
+ bb.statements[opt.bin_op_stmt_idx].make_nop();
+ } else {
+ // if the integer being compared to a const integral is being moved into the comparison,
+ // e.g `_2 = Eq(move _3, const 'x');`
+ // we want to avoid making a double move later on in the switchInt on _3.
+ // So to avoid `switchInt(move _3) -> ['x': bb2, otherwise: bb1];`,
+ // we convert the move in the comparison statement to a copy.
+
+ // unwrap is safe as we know this statement is an assign
+ let box (_, rhs) = bb.statements[opt.bin_op_stmt_idx].kind.as_assign_mut().unwrap();
+
+ use Operand::*;
+ match rhs {
+ Rvalue::BinaryOp(_, ref mut left @ Move(_), Constant(_)) => {
+ *left = Copy(opt.to_switch_on);
+ }
+ Rvalue::BinaryOp(_, Constant(_), ref mut right @ Move(_)) => {
+ *right = Copy(opt.to_switch_on);
+ }
+ _ => (),
+ }
+ }
+
+ // remove StorageDead (if it exists) being used in the assign of the comparison
+ for (stmt_idx, stmt) in bb.statements.iter().enumerate() {
+ if !matches!(stmt.kind, StatementKind::StorageDead(local) if local == opt.to_switch_on.local)
+ {
+ continue;
+ }
+ storage_deads_to_remove.push((stmt_idx, opt.bb_idx))
+ }
+ }
+
+ for (idx, bb_idx) in storage_deads_to_remove {
+ body.basic_blocks_mut()[bb_idx].statements[idx].make_nop();
+ }
+
+ for (idx, stmt) in storage_deads_to_insert {
+ body.basic_blocks_mut()[idx].statements.insert(0, stmt);
+ }
+ }
+}
+
+struct OptimizationFinder<'a, 'tcx> {
+ body: &'a Body<'tcx>,
+}
+
+impl<'a, 'tcx> OptimizationFinder<'a, 'tcx> {
+ fn find_optimizations(&self) -> Vec<OptimizationInfo<'tcx>> {
+ self.body
+ .basic_blocks()
+ .iter_enumerated()
+ .filter_map(|(bb_idx, bb)| {
+ // find switch
+ let (place_switched_on, values, targets, place_switched_on_moved) = match &bb
+ .terminator()
+ .kind
+ {
+ rustc_middle::mir::TerminatorKind::SwitchInt {
+ discr, values, targets, ..
+ } => Some((discr.place()?, values, targets, discr.is_move())),
+ _ => None,
+ }?;
+
+ // find the statement that assigns the place being switched on
+ bb.statements.iter().enumerate().rev().find_map(|(stmt_idx, stmt)| {
+ match &stmt.kind {
+ rustc_middle::mir::StatementKind::Assign(box (lhs, rhs))
+ if *lhs == place_switched_on =>
+ {
+ match rhs {
+ Rvalue::BinaryOp(op @ (BinOp::Eq | BinOp::Ne), left, right) => {
+ let (branch_value_scalar, branch_value_ty, to_switch_on) =
+ find_branch_value_info(left, right)?;
+
+ Some(OptimizationInfo {
+ bin_op_stmt_idx: stmt_idx,
+ bb_idx,
+ can_remove_bin_op_stmt: place_switched_on_moved,
+ to_switch_on,
+ branch_value_scalar,
+ branch_value_ty,
+ op: *op,
+ values: values.clone().into_owned(),
+ targets: targets.clone(),
+ })
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ })
+ })
+ .collect()
+ }
+}
+
+fn find_branch_value_info<'tcx>(
+ left: &Operand<'tcx>,
+ right: &Operand<'tcx>,
+) -> Option<(Scalar, Ty<'tcx>, Place<'tcx>)> {
+ // check that either left or right is a constant.
+ // if any are, we can use the other to switch on, and the constant as a value in a switch
+ use Operand::*;
+ match (left, right) {
+ (Constant(branch_value), Copy(to_switch_on) | Move(to_switch_on))
+ | (Copy(to_switch_on) | Move(to_switch_on), Constant(branch_value)) => {
+ let branch_value_ty = branch_value.literal.ty;
+ // we only want to apply this optimization if we are matching on integrals (and chars), as it is not possible to switch on floats
+ if !branch_value_ty.is_integral() && !branch_value_ty.is_char() {
+ return None;
+ };
+ let branch_value_scalar = branch_value.literal.val.try_to_scalar()?;
+ Some((branch_value_scalar, branch_value_ty, *to_switch_on))
+ }
+ _ => None,
+ }
+}
+
+#[derive(Debug)]
+struct OptimizationInfo<'tcx> {
+ /// Basic block to apply the optimization
+ bb_idx: BasicBlock,
+ /// Statement index of Eq/Ne assignment that can be removed. None if the assignment can not be removed - i.e the statement is used later on
+ bin_op_stmt_idx: usize,
+ /// Can remove Eq/Ne assignment
+ can_remove_bin_op_stmt: bool,
+ /// Place that needs to be switched on. This place is of type integral
+ to_switch_on: Place<'tcx>,
+ /// Constant to use in switch target value
+ branch_value_scalar: Scalar,
+ /// Type of the constant value
+ branch_value_ty: Ty<'tcx>,
+ /// Either Eq or Ne
+ op: BinOp,
+ /// Current values used in the switch target. This needs to be replaced with the branch_value
+ values: Vec<u128>,
+ /// Current targets used in the switch
+ targets: Vec<BasicBlock>,
+}
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_target::abi::VariantIdx;
+use std::convert::TryFrom;
use std::iter::TrustedLen;
/// Expand `lhs = Rvalue::Aggregate(kind, operands)` into assignments to the fields.
.enumerate()
.map(move |(i, (op, ty))| {
let lhs_field = if let AggregateKind::Array(_) = kind {
- // FIXME(eddyb) `offset` should be u64.
- let offset = i as u32;
- assert_eq!(offset as usize, i);
+ let offset = u64::try_from(i).unwrap();
tcx.mk_place_elem(
lhs,
ProjectionElem::ConstantIndex {
offset,
- // FIXME(eddyb) `min_length` doesn't appear to be used.
min_length: offset + 1,
from_end: false,
},
use crate::util::patch::MirPatch;
use rustc_hir as hir;
-use rustc_hir::lang_items::{BoxFreeFnLangItem, DropTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_index::vec::Idx;
use rustc_middle::mir::*;
use rustc_middle::traits::Reveal;
use rustc_target::abi::VariantIdx;
use std::fmt;
-use std::convert::TryInto;
-
/// The value of an inserted drop flag.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DropFlagState {
/// If this returns `None`, elements of `path` will not get a dedicated drop flag.
///
/// This is only relevant for array patterns, which can move out of individual array elements.
- fn array_subpath(&self, path: Self::Path, index: u32, size: u32) -> Option<Self::Path>;
+ fn array_subpath(&self, path: Self::Path, index: u64, size: u64) -> Option<Self::Path>;
}
#[derive(Debug)]
fn destructor_call_block(&mut self, (succ, unwind): (BasicBlock, Unwind)) -> BasicBlock {
debug!("destructor_call_block({:?}, {:?})", self, succ);
let tcx = self.tcx();
- let drop_trait = tcx.require_lang_item(DropTraitLangItem, None);
+ let drop_trait = tcx.require_lang_item(LangItem::Drop, None);
let drop_fn = tcx.associated_items(drop_trait).in_definition_order().next().unwrap();
let ty = self.place_ty(self.place);
let substs = tcx.mk_substs_trait(ty, &[]);
let tcx = self.tcx();
if let Some(size) = opt_size {
- let size: u32 = size.try_into().unwrap_or_else(|_| {
- bug!("move out check isn't implemented for array sizes bigger than u32::MAX");
- });
let fields: Vec<(Place<'tcx>, Option<D::Path>)> = (0..size)
.map(|i| {
(
) -> BasicBlock {
let tcx = self.tcx();
let unit_temp = Place::from(self.new_temp(tcx.mk_unit()));
- let free_func = tcx.require_lang_item(BoxFreeFnLangItem, Some(self.source_info.span));
+ let free_func = tcx.require_lang_item(LangItem::BoxFree, Some(self.source_info.span));
let args = adt.variants[VariantIdx::new(0)]
.fields
.iter()
PatKind::Array { ref prefix, ref slice, ref suffix }
| PatKind::Slice { ref prefix, ref slice, ref suffix } => {
- let from = u32::try_from(prefix.len()).unwrap();
- let to = u32::try_from(suffix.len()).unwrap();
+ let from = u64::try_from(prefix.len()).unwrap();
+ let to = u64::try_from(suffix.len()).unwrap();
for subpattern in prefix {
self.visit_primary_bindings(subpattern, pattern_user_ty.clone().index(), f);
}
use crate::thir::pattern::compare_const_vals;
use crate::thir::*;
use rustc_data_structures::fx::FxIndexMap;
-use rustc_hir::RangeEnd;
+use rustc_hir::{LangItem, RangeEnd};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::*;
use rustc_middle::ty::util::IntTypeExt;
place: Place<'tcx>,
mut ty: Ty<'tcx>,
) {
- use rustc_hir::lang_items::EqTraitLangItem;
-
let mut expect = self.literal_operand(source_info.span, value);
let mut val = Operand::Copy(place);
_ => bug!("non_scalar_compare called on non-reference type: {}", ty),
};
- let eq_def_id = self.hir.tcx().require_lang_item(EqTraitLangItem, None);
+ let eq_def_id = self.hir.tcx().require_lang_item(LangItem::PartialEq, None);
let method = self.hir.trait_method(eq_def_id, sym::eq, deref_ty, &[deref_ty.into()]);
let bool_ty = self.hir.bool_ty();
Some(index)
}
- (
- &TestKind::SwitchInt { switch_ty: _, ref options },
- &PatKind::Range(range),
- ) => {
+ (&TestKind::SwitchInt { switch_ty: _, ref options }, &PatKind::Range(range)) => {
let not_contained =
self.values_not_contained_in_range(range, options).unwrap_or(false);
match_pairs.extend(prefix.iter().enumerate().map(|(idx, subpattern)| {
let elem =
- ProjectionElem::ConstantIndex { offset: idx as u32, min_length, from_end: false };
+ ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false };
let place = tcx.mk_place_elem(*place, elem);
MatchPair::new(place, subpattern)
}));
if let Some(subslice_pat) = opt_slice {
- let suffix_len = suffix.len() as u32;
+ let suffix_len = suffix.len() as u64;
let subslice = tcx.mk_place_elem(
*place,
ProjectionElem::Subslice {
- from: prefix.len() as u32,
+ from: prefix.len() as u64,
to: if exact_size { min_length - suffix_len } else { suffix_len },
from_end: !exact_size,
},
}
match_pairs.extend(suffix.iter().rev().enumerate().map(|(idx, subpattern)| {
- let end_offset = (idx + 1) as u32;
+ let end_offset = (idx + 1) as u64;
let elem = ProjectionElem::ConstantIndex {
offset: if exact_size { min_length - end_offset } else { end_offset },
min_length,
use rustc_errors::ErrorReported;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{GeneratorKind, HirIdMap, Node};
use rustc_index::vec::{Idx, IndexVec};
use rustc_infer::infer::TyCtxtInferExt;
// C-variadic fns also have a `VaList` input that's not listed in `fn_sig`
// (as it's created inside the body itself, not passed in from outside).
let ty = if fn_sig.c_variadic && index == fn_sig.inputs().len() {
- let va_list_did =
- tcx.require_lang_item(lang_items::VaListTypeLangItem, Some(arg.span));
+ let va_list_did = tcx.require_lang_item(LangItem::VaList, Some(arg.span));
tcx.type_of(va_list_did).subst(tcx, &[tcx.lifetimes.re_erased.into()])
} else {
let mut projs = closure_env_projs.clone();
projs.push(ProjectionElem::Field(Field::new(i), ty));
match capture {
- ty::UpvarCapture::ByValue => {}
+ ty::UpvarCapture::ByValue(_) => {}
ty::UpvarCapture::ByRef(..) => {
projs.push(ProjectionElem::Deref);
}
// ...but the upvar might be an `&T` or `&mut T` capture, at which
// point we need an implicit deref
match cx.typeck_results().upvar_capture(upvar_id) {
- ty::UpvarCapture::ByValue => field_kind,
+ ty::UpvarCapture::ByValue(_) => field_kind,
ty::UpvarCapture::ByRef(borrow) => ExprKind::Deref {
arg: Expr {
temp_lifetime,
kind: convert_var(cx, closure_expr, var_hir_id),
};
match upvar_capture {
- ty::UpvarCapture::ByValue => captured_var.to_ref(),
+ ty::UpvarCapture::ByValue(_) => captured_var.to_ref(),
ty::UpvarCapture::ByRef(upvar_borrow) => {
let borrow_kind = match upvar_borrow.kind {
ty::BorrowKind::ImmBorrow => BorrowKind::Shared,
use rustc_hir as hir;
-use rustc_hir::lang_items::EqTraitLangItem;
use rustc_index::vec::Idx;
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_middle::mir::Field;
// not *yet* implement `PartialEq`. So for now we leave this here.
let ty_is_partial_eq: bool = {
let partial_eq_trait_id =
- self.tcx().require_lang_item(EqTraitLangItem, Some(self.span));
+ self.tcx().require_lang_item(hir::LangItem::PartialEq, Some(self.span));
let obligation: PredicateObligation<'_> = predicate_for_trait_def(
self.tcx(),
self.param_env,
(' ' | '\n' | '\t', _) if eat_ws => {
skips.push(pos);
}
- ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => {
+ ('\\', Some((next_pos, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
skips.push(*next_pos);
let _ = s.next();
}
let var = self.variable(var_hir_id, upvar.span);
self.acc(self.s.exit_ln, var, ACC_READ | ACC_USE);
}
- ty::UpvarCapture::ByValue => {}
+ ty::UpvarCapture::ByValue(_) => {}
}
}
}
closure_expr_id: self.ir.body_owner,
};
match self.typeck_results.upvar_capture(upvar_id) {
- ty::UpvarCapture::ByValue => {}
+ ty::UpvarCapture::ByValue(_) => {}
ty::UpvarCapture::ByRef(..) => continue,
};
if self.used_on_entry(entry_ln, var) {
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::{self, LangItem};
use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS;
use rustc_middle::middle::lang_items::required;
use rustc_middle::ty::TyCtxt;
// They will never implicitly be added to the `missing` array unless we do
// so here.
if items.eh_personality().is_none() {
- items.missing.push(lang_items::EhPersonalityLangItem);
+ items.missing.push(LangItem::EhPersonality);
+ }
+ if tcx.sess.target.target.options.is_like_emscripten && items.eh_catch_typeinfo().is_none() {
+ items.missing.push(LangItem::EhCatchTypeinfo);
}
{
for (name, &item) in WEAK_ITEMS_REFS.iter() {
if missing.contains(&item) && required(tcx, item) && items.require(item).is_err() {
- if item == lang_items::PanicImplLangItem {
+ if item == LangItem::PanicImpl {
tcx.sess.err("`#[panic_handler]` function required, but not found");
- } else if item == lang_items::OomLangItem {
+ } else if item == LangItem::Oom {
tcx.sess.err("`#[alloc_error_handler]` function required, but not found");
} else {
tcx.sess.err(&format!("language item required, but not found: `{}`", name));
fn visit_foreign_item(&mut self, i: &hir::ForeignItem<'_>) {
let check_name = |attr, sym| self.tcx.sess.check_name(attr, sym);
- if let Some((lang_item, _)) = hir::lang_items::extract(check_name, &i.attrs) {
+ if let Some((lang_item, _)) = lang_items::extract(check_name, &i.attrs) {
self.register(lang_item, i.span);
}
intravisit::walk_foreign_item(self, i)
"select which borrowck is used (`mir` or `migrate`) (default: `migrate`)"),
borrowck_stats: bool = (false, parse_bool, [UNTRACKED],
"gather borrowck statistics (default: no)"),
+ cgu_partitioning_strategy: Option<String> = (None, parse_opt_string, [TRACKED],
+ "the codegen unit partitioning strategy to use"),
chalk: bool = (false, parse_bool, [TRACKED],
"enable the experimental Chalk-based trait solving engine"),
codegen_backend: Option<String> = (None, parse_opt_string, [TRACKED],
rust_2015_preview,
rust_2018_preview,
rust_begin_unwind,
+ rust_eh_catch_typeinfo,
rust_eh_personality,
rust_eh_register_frames,
rust_eh_unregister_frames,
-use crate::spec::{LinkerFlavor, Target, TargetResult};
+use crate::spec::{LinkerFlavor, LldFlavor, Target, TargetResult};
pub fn target() -> TargetResult {
let mut base = super::hermit_base::opts();
base.max_atomic_width = Some(128);
- base.unsupported_abis = super::arm_base::unsupported_abis();
- base.linker = Some("aarch64-hermit-gcc".to_string());
Ok(Target {
llvm_target: "aarch64-unknown-hermit".to_string(),
target_os: "hermit".to_string(),
target_env: String::new(),
target_vendor: "unknown".to_string(),
- linker_flavor: LinkerFlavor::Gcc,
+ linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
options: base,
})
}
--- /dev/null
+use crate::spec::{LinkerFlavor, Target, TargetOptions, TargetResult};
+
+/// A base target for AVR devices using the GNU toolchain.
+///
+/// Requires GNU avr-gcc and avr-binutils on the host system.
+pub fn target(target_cpu: String) -> TargetResult {
+ Ok(Target {
+ arch: "avr".to_string(),
+ data_layout: "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8".to_string(),
+ llvm_target: "avr-unknown-unknown".to_string(),
+ target_endian: "little".to_string(),
+ target_pointer_width: "16".to_string(),
+ linker_flavor: LinkerFlavor::Gcc,
+ target_os: "unknown".to_string(),
+ target_env: "".to_string(),
+ target_vendor: "unknown".to_string(),
+ target_c_int_width: 16.to_string(),
+ options: TargetOptions {
+ cpu: target_cpu.clone(),
+ exe_suffix: ".elf".to_string(),
+
+ linker: Some("avr-gcc".to_owned()),
+ dynamic_linking: false,
+ executables: true,
+ linker_is_gnu: true,
+ has_rpath: false,
+ position_independent_executables: false,
+ eh_frame_header: false,
+ pre_link_args: vec![(
+ LinkerFlavor::Gcc,
+ vec![
+ format!("-mmcu={}", target_cpu),
+ // We want to be able to strip as much executable code as possible
+ // from the linker command line, and this flag indicates to the
+ // linker that it can avoid linking in dynamic libraries that don't
+ // actually satisfy any symbols up to that point (as with many other
+ // resolutions the linker does). This option only applies to all
+ // following libraries so we're sure to pass it as one of the first
+ // arguments.
+ "-Wl,--as-needed".to_string(),
+ ],
+ )]
+ .into_iter()
+ .collect(),
+ late_link_args: vec![(LinkerFlavor::Gcc, vec!["-lgcc".to_owned()])]
+ .into_iter()
+ .collect(),
+ ..TargetOptions::default()
+ },
+ })
+}
--- /dev/null
+use crate::spec::TargetResult;
+
+pub fn target() -> TargetResult {
+ super::avr_gnu_base::target("atmega328".to_owned())
+}
+++ /dev/null
-use crate::spec::{LinkerFlavor, Target, TargetResult};
-
-pub fn target() -> TargetResult {
- Ok(Target {
- llvm_target: "avr-unknown-unknown".to_string(),
- target_endian: "little".to_string(),
- target_pointer_width: "16".to_string(),
- data_layout: "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8".to_string(),
- arch: "avr".to_string(),
- linker_flavor: LinkerFlavor::Gcc,
- target_os: "unknown".to_string(),
- target_env: "".to_string(),
- target_vendor: "unknown".to_string(),
- target_c_int_width: 16.to_string(),
- options: super::freestanding_base::opts(),
- })
-}
+++ /dev/null
-use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
-use std::default::Default;
-
-pub fn opts() -> TargetOptions {
- let mut args = LinkArgs::new();
-
- args.insert(
- LinkerFlavor::Gcc,
- vec![
- // We want to be able to strip as much executable code as possible
- // from the linker command line, and this flag indicates to the
- // linker that it can avoid linking in dynamic libraries that don't
- // actually satisfy any symbols up to that point (as with many other
- // resolutions the linker does). This option only applies to all
- // following libraries so we're sure to pass it as one of the first
- // arguments.
- "-Wl,--as-needed".to_string(),
- ],
- );
-
- TargetOptions {
- dynamic_linking: false,
- executables: true,
- linker_is_gnu: true,
- has_rpath: false,
- pre_link_args: args,
- position_independent_executables: false,
- eh_frame_header: false,
- ..Default::default()
- }
-}
pre_link_args,
panic_strategy: PanicStrategy::Abort,
position_independent_executables: true,
- relocation_model: RelocModel::Static,
+ static_position_independent_executables: true,
+ relocation_model: RelocModel::Pic,
target_family: None,
tls_model: TlsModel::InitialExec,
..Default::default()
pre_link_args,
panic_strategy: PanicStrategy::Abort,
position_independent_executables: true,
- relocation_model: RelocModel::Static,
+ static_position_independent_executables: true,
+ relocation_model: RelocModel::Pic,
target_family: None,
tls_model: TlsModel::InitialExec,
..Default::default()
mod apple_base;
mod apple_sdk_base;
mod arm_base;
+mod avr_gnu_base;
mod cloudabi_base;
mod dragonfly_base;
mod freebsd_base;
-mod freestanding_base;
mod fuchsia_base;
mod haiku_base;
mod hermit_base;
("aarch64-fuchsia", aarch64_fuchsia),
("x86_64-fuchsia", x86_64_fuchsia),
- ("avr-unknown-unknown", avr_unknown_unknown),
+ ("avr-unknown-gnu-atmega328", avr_unknown_gnu_atmega328),
("x86_64-unknown-l4re-uclibc", x86_64_unknown_l4re_uclibc),
use crate::traits::{self, TraitEngine, TraitEngineExt};
use rustc_hir as hir;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::traits::ObligationCause;
use rustc_middle::arena::ArenaAllocatable;
return ty.is_copy_modulo_regions(self.tcx.at(span), param_env);
}
- let copy_def_id = self.tcx.require_lang_item(lang_items::CopyTraitLangItem, None);
+ let copy_def_id = self.tcx.require_lang_item(LangItem::Copy, None);
// This can get called from typeck (by euv), and `moves_by_default`
// rightly refuses to work with inference variables, but
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
use rustc_middle::ty::{
self, suggest_constraining_type_param, AdtKind, DefIdTree, Infer, InferTy, ToPredicate, Ty,
if let Some(body_id) = self.tcx.hir().maybe_body_owned_by(item_id) {
let body = self.tcx.hir().body(body_id);
if let Some(hir::GeneratorKind::Async(_)) = body.generator_kind {
- let future_trait =
- self.tcx.require_lang_item(lang_items::FutureTraitLangItem, None);
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
let self_ty = self.resolve_vars_if_possible(&trait_ref.self_ty());
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::ErrorReported;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items::{
- DiscriminantTypeLangItem, FnOnceOutputLangItem, FnOnceTraitLangItem, GeneratorTraitLangItem,
-};
+use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder};
use rustc_middle::ty::subst::Subst;
let tcx = selcx.tcx();
- let gen_def_id = tcx.require_lang_item(GeneratorTraitLangItem, None);
+ let gen_def_id = tcx.require_lang_item(LangItem::Generator, None);
let predicate = super::util::generator_trait_ref_and_outputs(
tcx,
let self_ty = selcx.infcx().shallow_resolve(obligation.predicate.self_ty());
let substs = tcx.mk_substs([self_ty.into()].iter());
- let discriminant_def_id = tcx.require_lang_item(DiscriminantTypeLangItem, None);
+ let discriminant_def_id = tcx.require_lang_item(LangItem::Discriminant, None);
let predicate = ty::ProjectionPredicate {
projection_ty: ty::ProjectionTy { substs, item_def_id: discriminant_def_id },
debug!("confirm_callable_candidate({:?},{:?})", obligation, fn_sig);
- let fn_once_def_id = tcx.require_lang_item(FnOnceTraitLangItem, None);
- let fn_once_output_def_id = tcx.require_lang_item(FnOnceOutputLangItem, None);
+ let fn_once_def_id = tcx.require_lang_item(LangItem::FnOnce, None);
+ let fn_once_output_def_id = tcx.require_lang_item(LangItem::FnOnceOutput, None);
let predicate = super::util::closure_trait_ref_and_return_type(
tcx,
//! [rustc dev guide]:
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_index::bit_set::GrowableBitSet;
use rustc_infer::infer::InferOk;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst, SubstsRef};
// We can only make objects from sized types.
let tr = ty::TraitRef::new(
- tcx.require_lang_item(lang_items::SizedTraitLangItem, None),
+ tcx.require_lang_item(LangItem::Sized, None),
tcx.mk_substs_trait(source, &[]),
);
nested.push(predicate_to_obligation(tr.without_const().to_predicate(tcx)));
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
-use rustc_hir::lang_items::{StructuralPeqTraitLangItem, StructuralTeqTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt, TypeFoldable, TypeVisitor};
use rustc_span::Span;
let mut fulfillment_cx = traits::FulfillmentContext::new();
// require `#[derive(PartialEq)]`
let structural_peq_def_id =
- infcx.tcx.require_lang_item(StructuralPeqTraitLangItem, Some(cause.span));
+ infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span));
fulfillment_cx.register_bound(
infcx,
ty::ParamEnv::empty(),
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
let structural_teq_def_id =
- infcx.tcx.require_lang_item(StructuralTeqTraitLangItem, Some(cause.span));
+ infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span));
fulfillment_cx.register_bound(
infcx,
ty::ParamEnv::empty(),
use crate::traits;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness};
use rustc_span::Span;
if !subty.has_escaping_bound_vars() {
let cause = self.cause(cause);
let trait_ref = ty::TraitRef {
- def_id: self.infcx.tcx.require_lang_item(lang_items::SizedTraitLangItem, None),
+ def_id: self.infcx.tcx.require_lang_item(LangItem::Sized, None),
substs: self.infcx.tcx.mk_substs_trait(subty, &[]),
};
self.out.push(traits::Obligation::new(
//! Queries for checking whether a type implements one of a few common traits.
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits;
fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
- is_item_raw(tcx, query, lang_items::CopyTraitLangItem)
+ is_item_raw(tcx, query, LangItem::Copy)
}
fn is_sized_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
- is_item_raw(tcx, query, lang_items::SizedTraitLangItem)
+ is_item_raw(tcx, query, LangItem::Sized)
}
fn is_freeze_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
- is_item_raw(tcx, query, lang_items::FreezeTraitLangItem)
+ is_item_raw(tcx, query, LangItem::Freeze)
}
fn is_item_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
- item: lang_items::LangItem,
+ item: LangItem,
) -> bool {
let (param_env, ty) = query.into_parts();
let trait_def_id = tcx.require_lang_item(item, None);
use rustc_hir::def::{CtorOf, DefKind, Namespace, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::{walk_generics, Visitor as _};
-use rustc_hir::lang_items::SizedTraitLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{Constness, GenericArg, GenericArgs};
use rustc_middle::ty::subst::{self, InternalSubsts, Subst, SubstsRef};
use rustc_middle::ty::GenericParamDefKind;
}
}
- let kind_id = tcx.lang_items().require(SizedTraitLangItem);
+ let kind_id = tcx.lang_items().require(LangItem::Sized);
match unbound {
Some(tpb) => {
// FIXME(#8559) currently requires the unbound to be built-in.
};
// Type check the descriminant and get its type.
- let scrut_ty = if force_scrutinee_bool {
+ let scrutinee_ty = if force_scrutinee_bool {
// Here we want to ensure:
//
// 1. That default match bindings are *not* accepted in the condition of an
// #55810: Type check patterns first so we get types for all bindings.
for arm in arms {
- self.check_pat_top(&arm.pat, scrut_ty, Some(scrut.span), true);
+ self.check_pat_top(&arm.pat, scrutinee_ty, Some(scrut.span), true);
}
// Now typecheck the blocks.
use rustc_ast as ast;
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorReported};
use rustc_hir as hir;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::cast::{CastKind, CastTy};
use rustc_middle::ty::error::TypeError;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn type_is_known_to_be_sized_modulo_regions(&self, ty: Ty<'tcx>, span: Span) -> bool {
- let lang_item = self.tcx.require_lang_item(lang_items::SizedTraitLangItem, None);
+ let lang_item = self.tcx.require_lang_item(LangItem::Sized, None);
traits::type_known_to_meet_bound_modulo_regions(self, self.param_env, ty, lang_item, span)
}
}
use crate::astconv::AstConv;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items::{FutureTraitLangItem, GeneratorTraitLangItem};
+use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::LateBoundRegionConversionTime;
use rustc_infer::infer::{InferOk, InferResult};
let trait_ref = projection.to_poly_trait_ref(tcx);
let is_fn = tcx.fn_trait_kind_from_lang_item(trait_ref.def_id()).is_some();
- let gen_trait = tcx.require_lang_item(GeneratorTraitLangItem, cause_span);
+ let gen_trait = tcx.require_lang_item(LangItem::Generator, cause_span);
let is_gen = gen_trait == trait_ref.def_id();
if !is_fn && !is_gen {
debug!("deduce_sig_from_projection: not fn or generator");
// Check that this is a projection from the `Future` trait.
let trait_ref = predicate.projection_ty.trait_ref(self.tcx);
- let future_trait = self.tcx.require_lang_item(FutureTraitLangItem, Some(cause_span));
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, Some(cause_span));
if trait_ref.def_id != future_trait {
debug!("deduce_future_output_from_projection: not a future");
return None;
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir as hir;
-use rustc_hir::lang_items::CloneTraitLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{is_range_literal, Node};
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::{self, AssocItem, Ty, TypeAndMut};
if self.can_coerce(ref_ty, expected) {
let mut sugg_sp = sp;
if let hir::ExprKind::MethodCall(ref segment, sp, ref args, _) = expr.kind {
- let clone_trait = self.tcx.require_lang_item(CloneTraitLangItem, Some(sp));
+ let clone_trait = self.tcx.require_lang_item(LangItem::Clone, Some(sp));
if let ([arg], Some(true), sym::clone) = (
&args[..],
self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map(
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, QPath};
use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_trait_selection::traits::{self, ObligationCauseCode};
+use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext};
use std::fmt::Display;
// Try alternative arbitrary self types that could fulfill this call.
// FIXME: probe for all types that *could* be arbitrary self-types, not
// just this list.
- try_alt_rcvr(&mut err, self.tcx.mk_lang_item(rcvr_t, lang_items::OwnedBoxLangItem));
- try_alt_rcvr(&mut err, self.tcx.mk_lang_item(rcvr_t, lang_items::PinTypeLangItem));
+ try_alt_rcvr(&mut err, self.tcx.mk_lang_item(rcvr_t, LangItem::OwnedBox));
+ try_alt_rcvr(&mut err, self.tcx.mk_lang_item(rcvr_t, LangItem::Pin));
try_alt_rcvr(&mut err, self.tcx.mk_diagnostic_item(rcvr_t, sym::Arc));
try_alt_rcvr(&mut err, self.tcx.mk_diagnostic_item(rcvr_t, sym::Rc));
}
kind_name: &str,
ty_span: Span,
) {
- if variant.recovered {
+ if variant.is_recovered() {
self.set_tainted_by_errors();
return;
}
self.tcx().ty_error()
}
+ fn suggest_await_on_field_access(
+ &self,
+ err: &mut DiagnosticBuilder<'_>,
+ field_ident: Ident,
+ base: &'tcx hir::Expr<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ def_id: DefId,
+ ) {
+ let param_env = self.tcx().param_env(def_id);
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
+ // Future::Output
+ let item_def_id =
+ self.tcx.associated_items(future_trait).in_definition_order().next().unwrap().def_id;
+
+ let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
+ debug!("suggest_await_on_field_access: projection_ty={:?}", projection_ty);
+
+ let cause = self.misc(expr.span);
+ let mut selcx = SelectionContext::new(&self.infcx);
+
+ let mut obligations = vec![];
+ if let Some(projection_ty) = projection_ty {
+ let normalized_ty = rustc_trait_selection::traits::normalize_projection_type(
+ &mut selcx,
+ param_env,
+ projection_ty,
+ cause,
+ 0,
+ &mut obligations,
+ );
+ debug!(
+ "suggest_await_on_field_access: normalized_ty={:?}, ty_kind={:?}",
+ self.resolve_vars_if_possible(&normalized_ty),
+ normalized_ty.kind,
+ );
+ if let ty::Adt(def, _) = normalized_ty.kind {
+ // no field access on enum type
+ if !def.is_enum() {
+ if def.non_enum_variant().fields.iter().any(|field| field.ident == field_ident)
+ {
+ err.span_suggestion_verbose(
+ base.span.shrink_to_hi(),
+ "consider awaiting before field access",
+ ".await".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ }
+ }
+
fn ban_nonexisting_field(
&self,
field: Ident,
expr: &'tcx hir::Expr<'tcx>,
expr_t: Ty<'tcx>,
) {
+ debug!(
+ "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
+ field, base, expr, expr_t
+ );
let mut err = self.no_such_field_err(field.span, field, expr_t);
match expr_t.peel_refs().kind {
ty::Param(param_ty) => {
self.point_at_param_definition(&mut err, param_ty);
}
+ ty::Opaque(def_id, _) => {
+ self.suggest_await_on_field_access(&mut err, field, base, expr, def_id);
+ }
_ => {}
}
use rustc_hir::def::{DefKind, Namespace, Res};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
use rustc_hir::intravisit;
-use rustc_hir::lang_items::FnOnceTraitLangItem;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, Node, QPath};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::hir::map as hir_map;
use rustc_span::{source_map, FileName, Span};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::Obligation;
+use rustc_trait_selection::traits::SelectionContext;
use std::cmp::Ordering;
ty::Closure(..) | ty::FnDef(..) | ty::FnPtr(_) => true,
// If it's not a simple function, look for things which implement `FnOnce`.
_ => {
- let fn_once = match tcx.lang_items().require(FnOnceTraitLangItem) {
+ let fn_once = match tcx.lang_items().require(LangItem::FnOnce) {
Ok(fn_once) => fn_once,
Err(..) => return false,
};
actual.prefix_string(),
ty_str,
);
+ if let Mode::MethodCall = mode {
+ if let SelfSource::MethodCall(call) = source {
+ self.suggest_await_before_method(
+ &mut err, item_name, actual, call, span,
+ );
+ }
+ }
if let Some(span) =
tcx.sess.confused_type_with_std_module.borrow().get(&span)
{
}
}
+ fn suggest_await_before_method(
+ &self,
+ err: &mut DiagnosticBuilder<'_>,
+ item_name: Ident,
+ ty: Ty<'tcx>,
+ call: &hir::Expr<'_>,
+ span: Span,
+ ) {
+ if let ty::Opaque(def_id, _) = ty.kind {
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
+ // Future::Output
+ let item_def_id = self
+ .tcx
+ .associated_items(future_trait)
+ .in_definition_order()
+ .next()
+ .unwrap()
+ .def_id;
+
+ let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
+ let cause = self.misc(span);
+ let mut selcx = SelectionContext::new(&self.infcx);
+ let mut obligations = vec![];
+ if let Some(projection_ty) = projection_ty {
+ let normalized_ty = rustc_trait_selection::traits::normalize_projection_type(
+ &mut selcx,
+ self.param_env,
+ projection_ty,
+ cause,
+ 0,
+ &mut obligations,
+ );
+ debug!(
+ "suggest_await_before_method: normalized_ty={:?}, ty_kind={:?}",
+ self.resolve_vars_if_possible(&normalized_ty),
+ normalized_ty.kind,
+ );
+ let method_exists = self.method_exists(item_name, normalized_ty, call.hir_id, true);
+ debug!("suggest_await_before_method: is_method_exist={}", method_exists);
+ if method_exists {
+ err.span_suggestion_verbose(
+ span.shrink_to_lo(),
+ "consider awaiting before this method call",
+ "await.".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ }
+
fn suggest_use_candidates(
&self,
err: &mut DiagnosticBuilder<'_>,
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::itemlikevisit::ItemLikeVisitor;
-use rustc_hir::lang_items::{
- FutureTraitLangItem, PinTypeLangItem, SizedTraitLangItem, VaListTypeLangItem,
-};
+use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, GenericArg, HirIdMap, ItemKind, Node, PatKind, QPath};
use rustc_index::bit_set::BitSet;
use rustc_index::vec::Idx;
// (as it's created inside the body itself, not passed in from outside).
let maybe_va_list = if fn_sig.c_variadic {
let span = body.params.last().unwrap().span;
- let va_list_did = tcx.require_lang_item(VaListTypeLangItem, Some(span));
+ let va_list_did = tcx.require_lang_item(LangItem::VaList, Some(span));
let region = fcx.next_region_var(RegionVariableOrigin::MiscVariable(span));
Some(tcx.type_of(va_list_did).subst(tcx, &[region.into()]))
code: traits::ObligationCauseCode<'tcx>,
) {
if !ty.references_error() {
- let lang_item = self.tcx.require_lang_item(SizedTraitLangItem, None);
+ let lang_item = self.tcx.require_lang_item(LangItem::Sized, None);
self.require_type_meets(ty, span, code, lang_item);
}
}
_ => {}
}
let boxed_found = self.tcx.mk_box(found);
- let new_found = self.tcx.mk_lang_item(boxed_found, PinTypeLangItem).unwrap();
+ let new_found = self.tcx.mk_lang_item(boxed_found, LangItem::Pin).unwrap();
if let (true, Ok(snippet)) = (
self.can_coerce(new_found, expected),
self.sess().source_map().span_to_snippet(expr.span),
let sp = expr.span;
// Check for `Future` implementations by constructing a predicate to
// prove: `<T as Future>::Output == U`
- let future_trait = self.tcx.require_lang_item(FutureTraitLangItem, Some(sp));
+ let future_trait = self.tcx.require_lang_item(LangItem::Future, Some(sp));
let item_def_id = self
.tcx
.associated_items(future_trait)
.filter(|ident| !used_fields.contains_key(&ident))
.collect::<Vec<_>>();
- let inexistent_fields_err = if !inexistent_fields.is_empty() && !variant.recovered {
+ let inexistent_fields_err = if !(inexistent_fields.is_empty() || variant.is_recovered()) {
Some(self.error_inexistent_fields(
adt.variant_descr(),
&inexistent_fields,
return;
}
}
- ty::UpvarCapture::ByValue => {}
+ ty::UpvarCapture::ByValue(_) => {}
}
let fn_hir_id = self.tcx.hir().local_def_id_to_hir_id(upvar_id.closure_expr_id);
let ty = self.resolve_node_type(fn_hir_id);
use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarSubsts};
use rustc_span::{Span, Symbol};
+use std::collections::hash_map::Entry;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub fn closure_analyze(&self, body: &'tcx hir::Body<'tcx>) {
closure_captures.insert(var_hir_id, upvar_id);
let capture_kind = match capture_clause {
- hir::CaptureBy::Value => ty::UpvarCapture::ByValue,
+ hir::CaptureBy::Value => ty::UpvarCapture::ByValue(None),
hir::CaptureBy::Ref => {
let origin = UpvarRegion(upvar_id, span);
let upvar_region = self.next_region_var(origin);
debug!("var_id={:?} upvar_ty={:?} capture={:?}", var_hir_id, upvar_ty, capture);
match capture {
- ty::UpvarCapture::ByValue => upvar_ty,
+ ty::UpvarCapture::ByValue(_) => upvar_ty,
ty::UpvarCapture::ByRef(borrow) => tcx.mk_ref(
borrow.region,
ty::TypeAndMut { ty: upvar_ty, mutbl: borrow.kind.to_mutbl_lossy() },
debug!("adjust_upvar_borrow_kind_for_consume: upvar={:?}", upvar_id);
+ let usage_span = tcx.hir().span(place_with_id.hir_id);
+
// To move out of an upvar, this must be a FnOnce closure
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnOnce,
- tcx.hir().span(place_with_id.hir_id),
+ usage_span,
var_name(tcx, upvar_id.var_path.hir_id),
);
- self.adjust_upvar_captures.insert(upvar_id, ty::UpvarCapture::ByValue);
+ // In a case like `let pat = upvar`, don't use the span
+ // of the pattern, as this just looks confusing.
+ let by_value_span = match tcx.hir().get(place_with_id.hir_id) {
+ hir::Node::Pat(_) => None,
+ _ => Some(usage_span),
+ };
+
+ let new_capture = ty::UpvarCapture::ByValue(by_value_span);
+ match self.adjust_upvar_captures.entry(upvar_id) {
+ Entry::Occupied(mut e) => {
+ match e.get() {
+ // We always overwrite `ByRef`, since we require
+ // that the upvar be available by value.
+ //
+ // If we had a previous by-value usage without a specific
+ // span, use ours instead. Otherwise, keep the first span
+ // we encountered, since there isn't an obviously better one.
+ ty::UpvarCapture::ByRef(_) | ty::UpvarCapture::ByValue(None) => {
+ e.insert(new_capture);
+ }
+ _ => {}
+ }
+ }
+ Entry::Vacant(e) => {
+ e.insert(new_capture);
+ }
+ }
}
/// Indicates that `place_with_id` is being directly mutated (e.g., assigned
);
match upvar_capture {
- ty::UpvarCapture::ByValue => {
+ ty::UpvarCapture::ByValue(_) => {
// Upvar is already by-value, the strongest criteria.
}
ty::UpvarCapture::ByRef(mut upvar_borrow) => {
use rustc_hir::intravisit as hir_visit;
use rustc_hir::intravisit::Visitor;
use rustc_hir::itemlikevisit::ParItemLikeVisitor;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::ItemKind;
use rustc_middle::hir::map as hir_map;
use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts, Subst};
let last = idx == variant.fields.len() - 1;
fcx.register_bound(
field.ty,
- fcx.tcx.require_lang_item(lang_items::SizedTraitLangItem, None),
+ fcx.tcx.require_lang_item(LangItem::Sized, None),
traits::ObligationCause::new(
field.span,
fcx.body_id,
if forbid_unsized {
fcx.register_bound(
item_ty,
- fcx.tcx.require_lang_item(lang_items::SizedTraitLangItem, None),
+ fcx.tcx.require_lang_item(LangItem::Sized, None),
traits::ObligationCause::new(ty_span, fcx.body_id, traits::MiscObligation),
);
}
// The first type is `receiver_ty`, which we know its not equal to `self_ty`; skip it.
autoderef.next();
- let receiver_trait_def_id = fcx.tcx.require_lang_item(lang_items::ReceiverTraitLangItem, None);
+ let receiver_trait_def_id = fcx.tcx.require_lang_item(LangItem::Receiver, None);
// Keep dereferencing `receiver_ty` until we get to `self_ty`.
loop {
fn visit_upvar_capture_map(&mut self) {
for (upvar_id, upvar_capture) in self.fcx.typeck_results.borrow().upvar_capture_map.iter() {
let new_upvar_capture = match *upvar_capture {
- ty::UpvarCapture::ByValue => ty::UpvarCapture::ByValue,
+ ty::UpvarCapture::ByValue(span) => ty::UpvarCapture::ByValue(span),
ty::UpvarCapture::ByRef(ref upvar_borrow) => {
ty::UpvarCapture::ByRef(ty::UpvarBorrow {
kind: upvar_borrow.kind,
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::lang_items::{
- CoerceUnsizedTraitLangItem, DispatchFromDynTraitLangItem, UnsizeTraitLangItem,
-};
+use rustc_hir::lang_items::LangItem;
use rustc_hir::ItemKind;
use rustc_infer::infer;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
let impl_hir_id = tcx.hir().local_def_id_to_hir_id(impl_did);
let span = tcx.hir().span(impl_hir_id);
- let dispatch_from_dyn_trait = tcx.require_lang_item(DispatchFromDynTraitLangItem, Some(span));
+ let dispatch_from_dyn_trait = tcx.require_lang_item(LangItem::DispatchFromDyn, Some(span));
let source = tcx.type_of(impl_did);
assert!(!source.has_escaping_bound_vars());
let impl_hir_id = tcx.hir().local_def_id_to_hir_id(impl_did.expect_local());
let span = tcx.hir().span(impl_hir_id);
- let coerce_unsized_trait = tcx.require_lang_item(CoerceUnsizedTraitLangItem, Some(span));
+ let coerce_unsized_trait = tcx.require_lang_item(LangItem::CoerceUnsized, Some(span));
- let unsize_trait = tcx.lang_items().require(UnsizeTraitLangItem).unwrap_or_else(|err| {
+ let unsize_trait = tcx.lang_items().require(LangItem::Unsize).unwrap_or_else(|err| {
tcx.sess.fatal(&format!("`CoerceUnsized` implementation {}", err));
});
generics_of,
predicates_of,
predicates_defined_on,
+ projection_ty_from_predicates,
explicit_predicates_of,
super_predicates_of,
type_param_predicates,
result
}
+fn projection_ty_from_predicates(
+ tcx: TyCtxt<'tcx>,
+ key: (
+ // ty_def_id
+ DefId,
+ // def_id of `N` in `<T as Trait>::N`
+ DefId,
+ ),
+) -> Option<ty::ProjectionTy<'tcx>> {
+ let (ty_def_id, item_def_id) = key;
+ let mut projection_ty = None;
+ for (predicate, _) in tcx.predicates_of(ty_def_id).predicates {
+ if let ty::PredicateAtom::Projection(projection_predicate) = predicate.skip_binders() {
+ if item_def_id == projection_predicate.projection_ty.item_def_id {
+ projection_ty = Some(projection_predicate.projection_ty);
+ break;
+ }
+ }
+ }
+ projection_ty
+}
+
fn trait_associated_item_predicates(
tcx: TyCtxt<'tcx>,
def_id: DefId,
var_id,
));
match upvar_capture {
- ty::UpvarCapture::ByValue => {
+ ty::UpvarCapture::ByValue(_) => {
let mode = copy_or_move(&self.mc, &captured_place);
self.delegate.consume(&captured_place, mode);
}
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::{self, Region, RegionVid, TypeFoldable};
use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult};
// The `Sized` trait must be handled specially, since we only display it when
// it is *not* required (i.e., '?Sized')
- let sized_trait = self.cx.tcx.require_lang_item(lang_items::SizedTraitLangItem, None);
+ let sized_trait = self.cx.tcx.require_lang_item(LangItem::Sized, None);
let mut replacer = RegionReplacer { vid_to_region: &vid_to_region, tcx };
fn is_fn_ty(&self, tcx: TyCtxt<'_>, ty: &Type) -> bool {
match &ty {
&&Type::ResolvedPath { ref did, .. } => {
- *did == tcx.require_lang_item(lang_items::FnTraitLangItem, None)
- || *did == tcx.require_lang_item(lang_items::FnMutTraitLangItem, None)
- || *did == tcx.require_lang_item(lang_items::FnOnceTraitLangItem, None)
+ *did == tcx.require_lang_item(LangItem::Fn, None)
+ || *did == tcx.require_lang_item(LangItem::FnMut, None)
+ || *did == tcx.require_lang_item(LangItem::FnOnce, None)
}
_ => false,
}
/// Renders the configuration for human display, as a short HTML description.
pub(crate) fn render_short_html(&self) -> String {
- let mut msg = Html(self, true).to_string();
+ let mut msg = Display(self, Format::ShortHtml).to_string();
if self.should_capitalize_first_letter() {
if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
msg[i..i + 1].make_ascii_uppercase();
pub(crate) fn render_long_html(&self) -> String {
let on = if self.should_use_with_in_description() { "with" } else { "on" };
- let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
+ let mut msg = format!(
+ "This is supported {} <strong>{}</strong>",
+ on,
+ Display(self, Format::LongHtml)
+ );
if self.should_append_only_to_description() {
msg.push_str(" only");
}
msg
}
+ /// Renders the configuration for long display, as a long plain text description.
+ pub(crate) fn render_long_plain(&self) -> String {
+ let on = if self.should_use_with_in_description() { "with" } else { "on" };
+
+ let mut msg = format!("This is supported {} {}", on, Display(self, Format::LongPlain));
+ if self.should_append_only_to_description() {
+ msg.push_str(" only");
+ }
+ msg
+ }
+
fn should_capitalize_first_letter(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Not(..) => true,
}
}
-/// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
-/// used.
-struct Html<'a>(&'a Cfg, bool);
+#[derive(Clone, Copy)]
+enum Format {
+ LongHtml,
+ LongPlain,
+ ShortHtml,
+}
+
+impl Format {
+ fn is_long(self) -> bool {
+ match self {
+ Format::LongHtml | Format::LongPlain => true,
+ Format::ShortHtml => false,
+ }
+ }
+
+ fn is_html(self) -> bool {
+ match self {
+ Format::LongHtml | Format::ShortHtml => true,
+ Format::LongPlain => false,
+ }
+ }
+}
+
+/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
+struct Display<'a>(&'a Cfg, Format);
fn write_with_opt_paren<T: fmt::Display>(
fmt: &mut fmt::Formatter<'_>,
Ok(())
}
-impl<'a> fmt::Display for Html<'a> {
+impl<'a> fmt::Display for Display<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self.0 {
Cfg::Not(ref child) => match **child {
if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
fmt.write_str(if i == 0 { "neither " } else { separator })?;
- write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
+ write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
}
Ok(())
}
- ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
- ref c => write!(fmt, "not ({})", Html(c, self.1)),
+ ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
+ ref c => write!(fmt, "not ({})", Display(c, self.1)),
},
Cfg::Any(ref sub_cfgs) => {
let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
+
+ let short_longhand = self.1.is_long() && {
+ let all_crate_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+ let all_target_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+ if all_crate_features {
+ fmt.write_str("crate features ")?;
+ true
+ } else if all_target_features {
+ fmt.write_str("target features ")?;
+ true
+ } else {
+ false
+ }
+ };
+
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(separator)?;
}
- write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
+ if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+ if self.1.is_html() {
+ write!(fmt, "<code>{}</code>", feat)?;
+ } else {
+ write!(fmt, "`{}`", feat)?;
+ }
+ } else {
+ write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
+ }
}
Ok(())
}
Cfg::All(ref sub_cfgs) => {
+ let short_longhand = self.1.is_long() && {
+ let all_crate_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
+ let all_target_features = sub_cfgs
+ .iter()
+ .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
+
+ if all_crate_features {
+ fmt.write_str("crate features ")?;
+ true
+ } else if all_target_features {
+ fmt.write_str("target features ")?;
+ true
+ } else {
+ false
+ }
+ };
+
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(" and ")?;
}
- write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
+ if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
+ if self.1.is_html() {
+ write!(fmt, "<code>{}</code>", feat)?;
+ } else {
+ write!(fmt, "`{}`", feat)?;
+ }
+ } else {
+ write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
+ }
}
Ok(())
}
},
(sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian),
(sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits),
- (sym::target_feature, Some(feat)) => {
- if self.1 {
- return write!(fmt, "<code>{}</code>", feat);
- } else {
+ (sym::target_feature, Some(feat)) => match self.1 {
+ Format::LongHtml => {
return write!(fmt, "target feature <code>{}</code>", feat);
}
- }
+ Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
+ Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+ },
+ (sym::feature, Some(feat)) => match self.1 {
+ Format::LongHtml => {
+ return write!(fmt, "crate feature <code>{}</code>", feat);
+ }
+ Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
+ Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
+ },
_ => "",
};
if !human_readable.is_empty() {
fmt.write_str(human_readable)
} else if let Some(v) = value {
- write!(
- fmt,
- "<code>{}=\"{}\"</code>",
- Escape(&name.as_str()),
- Escape(&v.as_str())
- )
- } else {
+ if self.1.is_html() {
+ write!(
+ fmt,
+ r#"<code>{}="{}"</code>"#,
+ Escape(&name.as_str()),
+ Escape(&v.as_str())
+ )
+ } else {
+ write!(fmt, r#"`{}="{}"`"#, name, v)
+ }
+ } else if self.1.is_html() {
write!(fmt, "<code>{}</code>", Escape(&name.as_str()))
+ } else {
+ write!(fmt, "`{}`", name)
}
}
}
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
-use rustc_hir::lang_items;
+use rustc_hir::lang_items::LangItem;
use rustc_hir::Mutability;
use rustc_index::vec::IndexVec;
use rustc_middle::middle::stability;
impl GenericBound {
pub fn maybe_sized(cx: &DocContext<'_>) -> GenericBound {
- let did = cx.tcx.require_lang_item(lang_items::SizedTraitLangItem, None);
+ let did = cx.tcx.require_lang_item(LangItem::Sized, None);
let empty = cx.tcx.intern_substs(&[]);
let path = external_path(cx, cx.tcx.item_name(did), Some(did), false, vec![], empty);
inline::record_extern_fqn(cx, did, TypeKind::Trait);
/// Codegen options strings to hand to the compiler.
pub codegen_options_strs: Vec<String>,
/// Debugging (`-Z`) options to pass to the compiler.
- pub debugging_options: DebuggingOptions,
+ pub debugging_opts: DebuggingOptions,
/// Debugging (`-Z`) options strings to pass to the compiler.
- pub debugging_options_strs: Vec<String>,
+ pub debugging_opts_strs: Vec<String>,
/// The target used to compile the crate against.
pub target: TargetTriple,
/// Edition used when reading the crate. Defaults to "2015". Also used by default when
let error_format = config::parse_error_format(&matches, color, json_rendered);
let codegen_options = build_codegen_options(matches, error_format);
- let debugging_options = build_debugging_options(matches, error_format);
+ let debugging_opts = build_debugging_options(matches, error_format);
- let diag = new_handler(error_format, None, &debugging_options);
+ let diag = new_handler(error_format, None, &debugging_opts);
// check for deprecated options
check_deprecated_options(&matches, &diag);
.iter()
.map(|s| SearchPath::from_cli_opt(s, error_format))
.collect();
- let externs = parse_externs(&matches, &debugging_options, error_format);
+ let externs = parse_externs(&matches, &debugging_opts, error_format);
let extern_html_root_urls = match parse_extern_html_roots(&matches) {
Ok(ex) => ex,
Err(err) => {
let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from);
let test_builder = matches.opt_str("test-builder").map(PathBuf::from);
let codegen_options_strs = matches.opt_strs("C");
- let debugging_options_strs = matches.opt_strs("Z");
+ let debugging_opts_strs = matches.opt_strs("Z");
let lib_strs = matches.opt_strs("L");
let extern_strs = matches.opt_strs("extern");
let runtool = matches.opt_str("runtool");
cfgs,
codegen_options,
codegen_options_strs,
- debugging_options,
- debugging_options_strs,
+ debugging_opts,
+ debugging_opts_strs,
target,
edition,
maybe_sysroot,
/// It returns a tuple containing:
/// * Vector of tuples of lints' name and their associated "max" level
/// * HashMap of lint id with their associated "max" level
-pub fn init_lints<F>(
+pub(crate) fn init_lints<F>(
mut allowed_lints: Vec<String>,
lint_opts: Vec<(String, lint::Level)>,
filter_call: F,
.filter_map(|lint| {
// Permit feature-gated lints to avoid feature errors when trying to
// allow all lints.
- if lint.name == warnings_lint_name || lint.feature_gate.is_some() {
+ if lint.feature_gate.is_some() || allowed_lints.iter().any(|l| lint.name == l) {
None
} else {
filter_call(lint)
externs,
mut cfgs,
codegen_options,
- debugging_options,
+ debugging_opts,
target,
edition,
maybe_sysroot,
let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
let no_crate_level_docs = rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS.name;
let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
+ let renamed_and_removed_lints = rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name;
+ let unknown_lints = rustc_lint::builtin::UNKNOWN_LINTS.name;
// In addition to those specific lints, we also need to allow those given through
// command line, otherwise they'll get ignored and we don't want that.
- let allowed_lints = vec![
+ let lints_to_show = vec![
intra_link_resolution_failure_name.to_owned(),
missing_docs.to_owned(),
missing_doc_example.to_owned(),
private_doc_tests.to_owned(),
no_crate_level_docs.to_owned(),
invalid_codeblock_attributes_name.to_owned(),
+ renamed_and_removed_lints.to_owned(),
+ unknown_lints.to_owned(),
];
- let (lint_opts, lint_caps) = init_lints(allowed_lints, lint_opts, |lint| {
+ let (lint_opts, lint_caps) = init_lints(lints_to_show, lint_opts, |lint| {
if lint.name == intra_link_resolution_failure_name
|| lint.name == invalid_codeblock_attributes_name
{
search_paths: libs,
crate_types,
lint_opts: if !display_warnings { lint_opts } else { vec![] },
- lint_cap: Some(lint_cap.unwrap_or_else(|| lint::Forbid)),
+ lint_cap,
cg: codegen_options,
externs,
target_triple: target,
unstable_features: UnstableFeatures::from_environment(),
actually_rustdoc: true,
- debugging_opts: debugging_options,
+ debugging_opts,
error_format,
edition,
describe_lints,
--- /dev/null
+use rustc_ast as ast;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::ErrorReported;
+use rustc_feature::UnstableFeatures;
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_hir::{HirId, CRATE_HIR_ID};
+use rustc_interface::interface;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::config::{self, CrateType};
+use rustc_session::{lint, DiagnosticOutput, Session};
+use rustc_span::edition::Edition;
+use rustc_span::source_map::SourceMap;
+use rustc_span::symbol::sym;
+use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
+use rustc_target::spec::TargetTriple;
+use tempfile::Builder as TempFileBuilder;
+
+use std::collections::HashMap;
+use std::env;
+use std::io::{self, Write};
+use std::panic;
+use std::path::PathBuf;
+use std::process::{self, Command, Stdio};
+use std::str;
+
+use crate::clean::Attributes;
+use crate::config::Options;
+use crate::core::init_lints;
+use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
+use crate::passes::span_of_attrs;
+
+#[derive(Clone, Default)]
+pub struct TestOptions {
+ /// Whether to disable the default `extern crate my_crate;` when creating doctests.
+ pub no_crate_inject: bool,
+ /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
+ /// the default `#![allow(unused)]`.
+ pub display_warnings: bool,
+ /// Additional crate-level attributes to add to doctests.
+ pub attrs: Vec<String>,
+}
+
+pub fn run(options: Options) -> Result<(), ErrorReported> {
+ let input = config::Input::File(options.input.clone());
+
+ let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
+
+ // In addition to those specific lints, we also need to allow those given through
+ // command line, otherwise they'll get ignored and we don't want that.
+ let allowed_lints = vec![invalid_codeblock_attributes_name.to_owned()];
+
+ let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| {
+ if lint.name == invalid_codeblock_attributes_name {
+ None
+ } else {
+ Some((lint.name_lower(), lint::Allow))
+ }
+ });
+
+ let crate_types =
+ if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
+
+ let sessopts = config::Options {
+ maybe_sysroot: options.maybe_sysroot.clone(),
+ search_paths: options.libs.clone(),
+ crate_types,
+ lint_opts: if !options.display_warnings { lint_opts } else { vec![] },
+ lint_cap: Some(options.lint_cap.clone().unwrap_or_else(|| lint::Forbid)),
+ cg: options.codegen_options.clone(),
+ externs: options.externs.clone(),
+ unstable_features: UnstableFeatures::from_environment(),
+ actually_rustdoc: true,
+ debugging_opts: config::DebuggingOptions { ..config::basic_debugging_options() },
+ edition: options.edition,
+ target_triple: options.target.clone(),
+ ..config::Options::default()
+ };
+
+ let mut cfgs = options.cfgs.clone();
+ cfgs.push("doc".to_owned());
+ cfgs.push("doctest".to_owned());
+ let config = interface::Config {
+ opts: sessopts,
+ crate_cfg: interface::parse_cfgspecs(cfgs),
+ input,
+ input_path: None,
+ output_file: None,
+ output_dir: None,
+ file_loader: None,
+ diagnostic_output: DiagnosticOutput::Default,
+ stderr: None,
+ crate_name: options.crate_name.clone(),
+ lint_caps,
+ register_lints: None,
+ override_queries: None,
+ registry: rustc_driver::diagnostics_registry(),
+ };
+
+ let mut test_args = options.test_args.clone();
+ let display_warnings = options.display_warnings;
+
+ let tests = interface::run_compiler(config, |compiler| {
+ compiler.enter(|queries| {
+ let lower_to_hir = queries.lower_to_hir()?;
+
+ let mut opts = scrape_test_config(lower_to_hir.peek().0);
+ opts.display_warnings |= options.display_warnings;
+ let enable_per_target_ignores = options.enable_per_target_ignores;
+ let mut collector = Collector::new(
+ queries.crate_name()?.peek().to_string(),
+ options,
+ false,
+ opts,
+ Some(compiler.session().parse_sess.clone_source_map()),
+ None,
+ enable_per_target_ignores,
+ );
+
+ let mut global_ctxt = queries.global_ctxt()?.take();
+
+ global_ctxt.enter(|tcx| {
+ let krate = tcx.hir().krate();
+
+ let mut hir_collector = HirCollector {
+ sess: compiler.session(),
+ collector: &mut collector,
+ map: tcx.hir(),
+ codes: ErrorCodes::from(
+ compiler.session().opts.unstable_features.is_nightly_build(),
+ ),
+ tcx,
+ };
+ hir_collector.visit_testable(
+ "".to_string(),
+ &krate.item.attrs,
+ CRATE_HIR_ID,
+ krate.item.span,
+ |this| {
+ intravisit::walk_crate(this, krate);
+ },
+ );
+ });
+ compiler.session().abort_if_errors();
+
+ let ret: Result<_, ErrorReported> = Ok(collector.tests);
+ ret
+ })
+ });
+ let tests = match tests {
+ Ok(tests) => tests,
+ Err(ErrorReported) => return Err(ErrorReported),
+ };
+
+ test_args.insert(0, "rustdoctest".to_string());
+
+ testing::test_main(
+ &test_args,
+ tests,
+ Some(testing::Options::new().display_output(display_warnings)),
+ );
+
+ Ok(())
+}
+
+// Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
+fn scrape_test_config(krate: &::rustc_hir::Crate<'_>) -> TestOptions {
+ use rustc_ast_pretty::pprust;
+
+ let mut opts =
+ TestOptions { no_crate_inject: false, display_warnings: false, attrs: Vec::new() };
+
+ let test_attrs: Vec<_> = krate
+ .item
+ .attrs
+ .iter()
+ .filter(|a| a.has_name(sym::doc))
+ .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
+ .filter(|a| a.has_name(sym::test))
+ .collect();
+ let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
+
+ for attr in attrs {
+ if attr.has_name(sym::no_crate_inject) {
+ opts.no_crate_inject = true;
+ }
+ if attr.has_name(sym::attr) {
+ if let Some(l) = attr.meta_item_list() {
+ for item in l {
+ opts.attrs.push(pprust::meta_list_item_to_string(item));
+ }
+ }
+ }
+ }
+
+ opts
+}
+
+/// Documentation test failure modes.
+enum TestFailure {
+ /// The test failed to compile.
+ CompileError,
+ /// The test is marked `compile_fail` but compiled successfully.
+ UnexpectedCompilePass,
+ /// The test failed to compile (as expected) but the compiler output did not contain all
+ /// expected error codes.
+ MissingErrorCodes(Vec<String>),
+ /// The test binary was unable to be executed.
+ ExecutionError(io::Error),
+ /// The test binary exited with a non-zero exit code.
+ ///
+ /// This typically means an assertion in the test failed or another form of panic occurred.
+ ExecutionFailure(process::Output),
+ /// The test is marked `should_panic` but the test binary executed successfully.
+ UnexpectedRunPass,
+}
+
+enum DirState {
+ Temp(tempfile::TempDir),
+ Perm(PathBuf),
+}
+
+impl DirState {
+ fn path(&self) -> &std::path::Path {
+ match self {
+ DirState::Temp(t) => t.path(),
+ DirState::Perm(p) => p.as_path(),
+ }
+ }
+}
+
+fn run_test(
+ test: &str,
+ cratename: &str,
+ line: usize,
+ options: Options,
+ should_panic: bool,
+ no_run: bool,
+ as_test_harness: bool,
+ runtool: Option<String>,
+ runtool_args: Vec<String>,
+ target: TargetTriple,
+ compile_fail: bool,
+ mut error_codes: Vec<String>,
+ opts: &TestOptions,
+ edition: Edition,
+ outdir: DirState,
+ path: PathBuf,
+) -> Result<(), TestFailure> {
+ let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts, edition);
+
+ let output_file = outdir.path().join("rust_out");
+
+ let rustc_binary = options
+ .test_builder
+ .as_deref()
+ .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
+ let mut compiler = Command::new(&rustc_binary);
+ compiler.arg("--crate-type").arg("bin");
+ for cfg in &options.cfgs {
+ compiler.arg("--cfg").arg(&cfg);
+ }
+ if let Some(sysroot) = options.maybe_sysroot {
+ compiler.arg("--sysroot").arg(sysroot);
+ }
+ compiler.arg("--edition").arg(&edition.to_string());
+ compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
+ compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
+ compiler.arg("-o").arg(&output_file);
+ if as_test_harness {
+ compiler.arg("--test");
+ }
+ for lib_str in &options.lib_strs {
+ compiler.arg("-L").arg(&lib_str);
+ }
+ for extern_str in &options.extern_strs {
+ compiler.arg("--extern").arg(&extern_str);
+ }
+ compiler.arg("-Ccodegen-units=1");
+ for codegen_options_str in &options.codegen_options_strs {
+ compiler.arg("-C").arg(&codegen_options_str);
+ }
+ for debugging_option_str in &options.debugging_opts_strs {
+ compiler.arg("-Z").arg(&debugging_option_str);
+ }
+ if no_run && !compile_fail {
+ compiler.arg("--emit=metadata");
+ }
+ compiler.arg("--target").arg(match target {
+ TargetTriple::TargetTriple(s) => s,
+ TargetTriple::TargetPath(path) => {
+ path.to_str().expect("target path must be valid unicode").to_string()
+ }
+ });
+
+ compiler.arg("-");
+ compiler.stdin(Stdio::piped());
+ compiler.stderr(Stdio::piped());
+
+ let mut child = compiler.spawn().expect("Failed to spawn rustc process");
+ {
+ let stdin = child.stdin.as_mut().expect("Failed to open stdin");
+ stdin.write_all(test.as_bytes()).expect("could write out test sources");
+ }
+ let output = child.wait_with_output().expect("Failed to read stdout");
+
+ struct Bomb<'a>(&'a str);
+ impl Drop for Bomb<'_> {
+ fn drop(&mut self) {
+ eprint!("{}", self.0);
+ }
+ }
+ let out = str::from_utf8(&output.stderr).unwrap();
+ let _bomb = Bomb(&out);
+ match (output.status.success(), compile_fail) {
+ (true, true) => {
+ return Err(TestFailure::UnexpectedCompilePass);
+ }
+ (true, false) => {}
+ (false, true) => {
+ if !error_codes.is_empty() {
+ error_codes.retain(|err| !out.contains(&format!("error[{}]: ", err)));
+
+ if !error_codes.is_empty() {
+ return Err(TestFailure::MissingErrorCodes(error_codes));
+ }
+ }
+ }
+ (false, false) => {
+ return Err(TestFailure::CompileError);
+ }
+ }
+
+ if no_run {
+ return Ok(());
+ }
+
+ // Run the code!
+ let mut cmd;
+
+ if let Some(tool) = runtool {
+ cmd = Command::new(tool);
+ cmd.args(runtool_args);
+ cmd.arg(output_file);
+ } else {
+ cmd = Command::new(output_file);
+ }
+
+ match cmd.output() {
+ Err(e) => return Err(TestFailure::ExecutionError(e)),
+ Ok(out) => {
+ if should_panic && out.status.success() {
+ return Err(TestFailure::UnexpectedRunPass);
+ } else if !should_panic && !out.status.success() {
+ return Err(TestFailure::ExecutionFailure(out));
+ }
+ }
+ }
+
+ Ok(())
+}
+
+/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
+/// lines before the test code begins.
+pub fn make_test(
+ s: &str,
+ cratename: Option<&str>,
+ dont_insert_main: bool,
+ opts: &TestOptions,
+ edition: Edition,
+) -> (String, usize) {
+ let (crate_attrs, everything_else, crates) = partition_source(s);
+ let everything_else = everything_else.trim();
+ let mut line_offset = 0;
+ let mut prog = String::new();
+
+ if opts.attrs.is_empty() && !opts.display_warnings {
+ // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
+ // lints that are commonly triggered in doctests. The crate-level test attributes are
+ // commonly used to make tests fail in case they trigger warnings, so having this there in
+ // that case may cause some tests to pass when they shouldn't have.
+ prog.push_str("#![allow(unused)]\n");
+ line_offset += 1;
+ }
+
+ // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
+ for attr in &opts.attrs {
+ prog.push_str(&format!("#![{}]\n", attr));
+ line_offset += 1;
+ }
+
+ // Now push any outer attributes from the example, assuming they
+ // are intended to be crate attributes.
+ prog.push_str(&crate_attrs);
+ prog.push_str(&crates);
+
+ // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
+ // crate already is included.
+ let result = rustc_driver::catch_fatal_errors(|| {
+ rustc_span::with_session_globals(edition, || {
+ use rustc_errors::emitter::EmitterWriter;
+ use rustc_errors::Handler;
+ use rustc_parse::maybe_new_parser_from_source_str;
+ use rustc_session::parse::ParseSess;
+ use rustc_span::source_map::FilePathMapping;
+
+ let filename = FileName::anon_source_code(s);
+ let source = crates + everything_else;
+
+ // Any errors in parsing should also appear when the doctest is compiled for real, so just
+ // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
+ let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let emitter =
+ EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
+ // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
+ let handler = Handler::with_emitter(false, None, box emitter);
+ let sess = ParseSess::with_span_handler(handler, sm);
+
+ let mut found_main = false;
+ let mut found_extern_crate = cratename.is_none();
+ let mut found_macro = false;
+
+ let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
+ Ok(p) => p,
+ Err(errs) => {
+ for mut err in errs {
+ err.cancel();
+ }
+
+ return (found_main, found_extern_crate, found_macro);
+ }
+ };
+
+ loop {
+ match parser.parse_item() {
+ Ok(Some(item)) => {
+ if !found_main {
+ if let ast::ItemKind::Fn(..) = item.kind {
+ if item.ident.name == sym::main {
+ found_main = true;
+ }
+ }
+ }
+
+ if !found_extern_crate {
+ if let ast::ItemKind::ExternCrate(original) = item.kind {
+ // This code will never be reached if `cratename` is none because
+ // `found_extern_crate` is initialized to `true` if it is none.
+ let cratename = cratename.unwrap();
+
+ match original {
+ Some(name) => found_extern_crate = name.as_str() == cratename,
+ None => found_extern_crate = item.ident.as_str() == cratename,
+ }
+ }
+ }
+
+ if !found_macro {
+ if let ast::ItemKind::MacCall(..) = item.kind {
+ found_macro = true;
+ }
+ }
+
+ if found_main && found_extern_crate {
+ break;
+ }
+ }
+ Ok(None) => break,
+ Err(mut e) => {
+ e.cancel();
+ break;
+ }
+ }
+ }
+
+ (found_main, found_extern_crate, found_macro)
+ })
+ });
+ let (already_has_main, already_has_extern_crate, found_macro) = match result {
+ Ok(result) => result,
+ Err(ErrorReported) => {
+ // If the parser panicked due to a fatal error, pass the test code through unchanged.
+ // The error will be reported during compilation.
+ return (s.to_owned(), 0);
+ }
+ };
+
+ // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
+ // see it. In that case, run the old text-based scan to see if they at least have a main
+ // function written inside a macro invocation. See
+ // https://github.com/rust-lang/rust/issues/56898
+ let already_has_main = if found_macro && !already_has_main {
+ s.lines()
+ .map(|line| {
+ let comment = line.find("//");
+ if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
+ })
+ .any(|code| code.contains("fn main"))
+ } else {
+ already_has_main
+ };
+
+ // Don't inject `extern crate std` because it's already injected by the
+ // compiler.
+ if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
+ if let Some(cratename) = cratename {
+ // Make sure its actually used if not included.
+ if s.contains(cratename) {
+ prog.push_str(&format!("extern crate {};\n", cratename));
+ line_offset += 1;
+ }
+ }
+ }
+
+ // FIXME: This code cannot yet handle no_std test cases yet
+ if dont_insert_main || already_has_main || prog.contains("![no_std]") {
+ prog.push_str(everything_else);
+ } else {
+ let returns_result = everything_else.trim_end().ends_with("(())");
+ let (main_pre, main_post) = if returns_result {
+ (
+ "fn main() { fn _inner() -> Result<(), impl core::fmt::Debug> {",
+ "}\n_inner().unwrap() }",
+ )
+ } else {
+ ("fn main() {\n", "\n}")
+ };
+ prog.extend([main_pre, everything_else, main_post].iter().cloned());
+ line_offset += 1;
+ }
+
+ debug!("final doctest:\n{}", prog);
+
+ (prog, line_offset)
+}
+
+// FIXME(aburka): use a real parser to deal with multiline attributes
+fn partition_source(s: &str) -> (String, String, String) {
+ #[derive(Copy, Clone, PartialEq)]
+ enum PartitionState {
+ Attrs,
+ Crates,
+ Other,
+ }
+ let mut state = PartitionState::Attrs;
+ let mut before = String::new();
+ let mut crates = String::new();
+ let mut after = String::new();
+
+ for line in s.lines() {
+ let trimline = line.trim();
+
+ // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
+ // shunted into "everything else"
+ match state {
+ PartitionState::Attrs => {
+ state = if trimline.starts_with("#![")
+ || trimline.chars().all(|c| c.is_whitespace())
+ || (trimline.starts_with("//") && !trimline.starts_with("///"))
+ {
+ PartitionState::Attrs
+ } else if trimline.starts_with("extern crate")
+ || trimline.starts_with("#[macro_use] extern crate")
+ {
+ PartitionState::Crates
+ } else {
+ PartitionState::Other
+ };
+ }
+ PartitionState::Crates => {
+ state = if trimline.starts_with("extern crate")
+ || trimline.starts_with("#[macro_use] extern crate")
+ || trimline.chars().all(|c| c.is_whitespace())
+ || (trimline.starts_with("//") && !trimline.starts_with("///"))
+ {
+ PartitionState::Crates
+ } else {
+ PartitionState::Other
+ };
+ }
+ PartitionState::Other => {}
+ }
+
+ match state {
+ PartitionState::Attrs => {
+ before.push_str(line);
+ before.push_str("\n");
+ }
+ PartitionState::Crates => {
+ crates.push_str(line);
+ crates.push_str("\n");
+ }
+ PartitionState::Other => {
+ after.push_str(line);
+ after.push_str("\n");
+ }
+ }
+ }
+
+ debug!("before:\n{}", before);
+ debug!("crates:\n{}", crates);
+ debug!("after:\n{}", after);
+
+ (before, after, crates)
+}
+
+pub trait Tester {
+ fn add_test(&mut self, test: String, config: LangString, line: usize);
+ fn get_line(&self) -> usize {
+ 0
+ }
+ fn register_header(&mut self, _name: &str, _level: u32) {}
+}
+
+pub struct Collector {
+ pub tests: Vec<testing::TestDescAndFn>,
+
+ // The name of the test displayed to the user, separated by `::`.
+ //
+ // In tests from Rust source, this is the path to the item
+ // e.g., `["std", "vec", "Vec", "push"]`.
+ //
+ // In tests from a markdown file, this is the titles of all headers (h1~h6)
+ // of the sections that contain the code block, e.g., if the markdown file is
+ // written as:
+ //
+ // ``````markdown
+ // # Title
+ //
+ // ## Subtitle
+ //
+ // ```rust
+ // assert!(true);
+ // ```
+ // ``````
+ //
+ // the `names` vector of that test will be `["Title", "Subtitle"]`.
+ names: Vec<String>,
+
+ options: Options,
+ use_headers: bool,
+ enable_per_target_ignores: bool,
+ cratename: String,
+ opts: TestOptions,
+ position: Span,
+ source_map: Option<Lrc<SourceMap>>,
+ filename: Option<PathBuf>,
+ visited_tests: HashMap<(String, usize), usize>,
+}
+
+impl Collector {
+ pub fn new(
+ cratename: String,
+ options: Options,
+ use_headers: bool,
+ opts: TestOptions,
+ source_map: Option<Lrc<SourceMap>>,
+ filename: Option<PathBuf>,
+ enable_per_target_ignores: bool,
+ ) -> Collector {
+ Collector {
+ tests: Vec::new(),
+ names: Vec::new(),
+ options,
+ use_headers,
+ enable_per_target_ignores,
+ cratename,
+ opts,
+ position: DUMMY_SP,
+ source_map,
+ filename,
+ visited_tests: HashMap::new(),
+ }
+ }
+
+ fn generate_name(&self, line: usize, filename: &FileName) -> String {
+ let mut item_path = self.names.join("::");
+ if !item_path.is_empty() {
+ item_path.push(' ');
+ }
+ format!("{} - {}(line {})", filename, item_path, line)
+ }
+
+ pub fn set_position(&mut self, position: Span) {
+ self.position = position;
+ }
+
+ fn get_filename(&self) -> FileName {
+ if let Some(ref source_map) = self.source_map {
+ let filename = source_map.span_to_filename(self.position);
+ if let FileName::Real(ref filename) = filename {
+ if let Ok(cur_dir) = env::current_dir() {
+ if let Ok(path) = filename.local_path().strip_prefix(&cur_dir) {
+ return path.to_owned().into();
+ }
+ }
+ }
+ filename
+ } else if let Some(ref filename) = self.filename {
+ filename.clone().into()
+ } else {
+ FileName::Custom("input".to_owned())
+ }
+ }
+}
+
+impl Tester for Collector {
+ fn add_test(&mut self, test: String, config: LangString, line: usize) {
+ let filename = self.get_filename();
+ let name = self.generate_name(line, &filename);
+ let cratename = self.cratename.to_string();
+ let opts = self.opts.clone();
+ let edition = config.edition.unwrap_or(self.options.edition);
+ let options = self.options.clone();
+ let runtool = self.options.runtool.clone();
+ let runtool_args = self.options.runtool_args.clone();
+ let target = self.options.target.clone();
+ let target_str = target.to_string();
+
+ // FIXME(#44940): if doctests ever support path remapping, then this filename
+ // needs to be the result of `SourceMap::span_to_unmapped_path`.
+ let path = match &filename {
+ FileName::Real(path) => path.local_path().to_path_buf(),
+ _ => PathBuf::from(r"doctest.rs"),
+ };
+
+ let outdir = if let Some(mut path) = options.persist_doctests.clone() {
+ // For example `module/file.rs` would become `module_file_rs`
+ let folder_name = filename
+ .to_string()
+ .chars()
+ .map(|c| if c == '/' || c == '.' { '_' } else { c })
+ .collect::<String>();
+
+ path.push(format!(
+ "{name}_{line}_{number}",
+ name = folder_name,
+ number = {
+ // Increases the current test number, if this file already
+ // exists or it creates a new entry with a test number of 0.
+ self.visited_tests
+ .entry((folder_name.clone(), line))
+ .and_modify(|v| *v += 1)
+ .or_insert(0)
+ },
+ line = line,
+ ));
+
+ std::fs::create_dir_all(&path)
+ .expect("Couldn't create directory for doctest executables");
+
+ DirState::Perm(path)
+ } else {
+ DirState::Temp(
+ TempFileBuilder::new()
+ .prefix("rustdoctest")
+ .tempdir()
+ .expect("rustdoc needs a tempdir"),
+ )
+ };
+
+ debug!("creating test {}: {}", name, test);
+ self.tests.push(testing::TestDescAndFn {
+ desc: testing::TestDesc {
+ name: testing::DynTestName(name),
+ ignore: match config.ignore {
+ Ignore::All => true,
+ Ignore::None => false,
+ Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
+ },
+ // compiler failures are test failures
+ should_panic: testing::ShouldPanic::No,
+ allow_fail: config.allow_fail,
+ test_type: testing::TestType::DocTest,
+ },
+ testfn: testing::DynTestFn(box move || {
+ let res = run_test(
+ &test,
+ &cratename,
+ line,
+ options,
+ config.should_panic,
+ config.no_run,
+ config.test_harness,
+ runtool,
+ runtool_args,
+ target,
+ config.compile_fail,
+ config.error_codes,
+ &opts,
+ edition,
+ outdir,
+ path,
+ );
+
+ if let Err(err) = res {
+ match err {
+ TestFailure::CompileError => {
+ eprint!("Couldn't compile the test.");
+ }
+ TestFailure::UnexpectedCompilePass => {
+ eprint!("Test compiled successfully, but it's marked `compile_fail`.");
+ }
+ TestFailure::UnexpectedRunPass => {
+ eprint!("Test executable succeeded, but it's marked `should_panic`.");
+ }
+ TestFailure::MissingErrorCodes(codes) => {
+ eprint!("Some expected error codes were not found: {:?}", codes);
+ }
+ TestFailure::ExecutionError(err) => {
+ eprint!("Couldn't run the test: {}", err);
+ if err.kind() == io::ErrorKind::PermissionDenied {
+ eprint!(" - maybe your tempdir is mounted with noexec?");
+ }
+ }
+ TestFailure::ExecutionFailure(out) => {
+ let reason = if let Some(code) = out.status.code() {
+ format!("exit code {}", code)
+ } else {
+ String::from("terminated by signal")
+ };
+
+ eprintln!("Test executable failed ({}).", reason);
+
+ // FIXME(#12309): An unfortunate side-effect of capturing the test
+ // executable's output is that the relative ordering between the test's
+ // stdout and stderr is lost. However, this is better than the
+ // alternative: if the test executable inherited the parent's I/O
+ // handles the output wouldn't be captured at all, even on success.
+ //
+ // The ordering could be preserved if the test process' stderr was
+ // redirected to stdout, but that functionality does not exist in the
+ // standard library, so it may not be portable enough.
+ let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
+ let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
+
+ if !stdout.is_empty() || !stderr.is_empty() {
+ eprintln!();
+
+ if !stdout.is_empty() {
+ eprintln!("stdout:\n{}", stdout);
+ }
+
+ if !stderr.is_empty() {
+ eprintln!("stderr:\n{}", stderr);
+ }
+ }
+ }
+ }
+
+ panic::resume_unwind(box ());
+ }
+ }),
+ });
+ }
+
+ fn get_line(&self) -> usize {
+ if let Some(ref source_map) = self.source_map {
+ let line = self.position.lo().to_usize();
+ let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
+ if line > 0 { line - 1 } else { line }
+ } else {
+ 0
+ }
+ }
+
+ fn register_header(&mut self, name: &str, level: u32) {
+ if self.use_headers {
+ // We use these headings as test names, so it's good if
+ // they're valid identifiers.
+ let name = name
+ .chars()
+ .enumerate()
+ .map(|(i, c)| {
+ if (i == 0 && rustc_lexer::is_id_start(c))
+ || (i != 0 && rustc_lexer::is_id_continue(c))
+ {
+ c
+ } else {
+ '_'
+ }
+ })
+ .collect::<String>();
+
+ // Here we try to efficiently assemble the header titles into the
+ // test name in the form of `h1::h2::h3::h4::h5::h6`.
+ //
+ // Suppose that originally `self.names` contains `[h1, h2, h3]`...
+ let level = level as usize;
+ if level <= self.names.len() {
+ // ... Consider `level == 2`. All headers in the lower levels
+ // are irrelevant in this new level. So we should reset
+ // `self.names` to contain headers until <h2>, and replace that
+ // slot with the new name: `[h1, name]`.
+ self.names.truncate(level);
+ self.names[level - 1] = name;
+ } else {
+ // ... On the other hand, consider `level == 5`. This means we
+ // need to extend `self.names` to contain five headers. We fill
+ // in the missing level (<h4>) with `_`. Thus `self.names` will
+ // become `[h1, h2, h3, "_", name]`.
+ if level - 1 > self.names.len() {
+ self.names.resize(level - 1, "_".to_owned());
+ }
+ self.names.push(name);
+ }
+ }
+ }
+}
+
+struct HirCollector<'a, 'hir, 'tcx> {
+ sess: &'a Session,
+ collector: &'a mut Collector,
+ map: Map<'hir>,
+ codes: ErrorCodes,
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
+ fn visit_testable<F: FnOnce(&mut Self)>(
+ &mut self,
+ name: String,
+ attrs: &[ast::Attribute],
+ hir_id: HirId,
+ sp: Span,
+ nested: F,
+ ) {
+ let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
+ if let Some(ref cfg) = attrs.cfg {
+ if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
+ return;
+ }
+ }
+
+ let has_name = !name.is_empty();
+ if has_name {
+ self.collector.names.push(name);
+ }
+
+ attrs.collapse_doc_comments();
+ attrs.unindent_doc_comments();
+ // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
+ // anything else, this will combine them for us.
+ if let Some(doc) = attrs.collapsed_doc_value() {
+ // Use the outermost invocation, so that doctest names come from where the docs were written.
+ let span = attrs
+ .span
+ .map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
+ .unwrap_or(DUMMY_SP);
+ self.collector.set_position(span);
+ markdown::find_testable_code(
+ &doc,
+ self.collector,
+ self.codes,
+ self.collector.enable_per_target_ignores,
+ Some(&crate::html::markdown::ExtraInfo::new(
+ &self.tcx,
+ hir_id,
+ span_of_attrs(&attrs).unwrap_or(sp),
+ )),
+ );
+ }
+
+ nested(self);
+
+ if has_name {
+ self.collector.names.pop();
+ }
+ }
+}
+
+impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::All(self.map)
+ }
+
+ fn visit_item(&mut self, item: &'hir hir::Item<'_>) {
+ let name = if let hir::ItemKind::Impl { ref self_ty, .. } = item.kind {
+ rustc_hir_pretty::id_to_string(&self.map, self_ty.hir_id)
+ } else {
+ item.ident.to_string()
+ };
+
+ self.visit_testable(name, &item.attrs, item.hir_id, item.span, |this| {
+ intravisit::walk_item(this, item);
+ });
+ }
+
+ fn visit_trait_item(&mut self, item: &'hir hir::TraitItem<'_>) {
+ self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
+ intravisit::walk_trait_item(this, item);
+ });
+ }
+
+ fn visit_impl_item(&mut self, item: &'hir hir::ImplItem<'_>) {
+ self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
+ intravisit::walk_impl_item(this, item);
+ });
+ }
+
+ fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem<'_>) {
+ self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
+ intravisit::walk_foreign_item(this, item);
+ });
+ }
+
+ fn visit_variant(
+ &mut self,
+ v: &'hir hir::Variant<'_>,
+ g: &'hir hir::Generics<'_>,
+ item_id: hir::HirId,
+ ) {
+ self.visit_testable(v.ident.to_string(), &v.attrs, v.id, v.span, |this| {
+ intravisit::walk_variant(this, v, g, item_id);
+ });
+ }
+
+ fn visit_struct_field(&mut self, f: &'hir hir::StructField<'_>) {
+ self.visit_testable(f.ident.to_string(), &f.attrs, f.hir_id, f.span, |this| {
+ intravisit::walk_struct_field(this, f);
+ });
+ }
+
+ fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef<'_>) {
+ self.visit_testable(
+ macro_def.ident.to_string(),
+ ¯o_def.attrs,
+ macro_def.hir_id,
+ macro_def.span,
+ |_| (),
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests;
--- /dev/null
+use super::{make_test, TestOptions};
+use rustc_span::edition::DEFAULT_EDITION;
+
+#[test]
+fn make_test_basic() {
+ //basic use: wraps with `fn main`, adds `#![allow(unused)]`
+ let opts = TestOptions::default();
+ let input = "assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_crate_name_no_use() {
+ // If you give a crate name but *don't* use it within the test, it won't bother inserting
+ // the `extern crate` statement.
+ let opts = TestOptions::default();
+ let input = "assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_crate_name() {
+ // If you give a crate name and use it within the test, it will insert an `extern crate`
+ // statement before `fn main`.
+ let opts = TestOptions::default();
+ let input = "use asdf::qwop;
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 3));
+}
+
+#[test]
+fn make_test_no_crate_inject() {
+ // Even if you do use the crate within the test, setting `opts.no_crate_inject` will skip
+ // adding it anyway.
+ let opts = TestOptions { no_crate_inject: true, display_warnings: false, attrs: vec![] };
+ let input = "use asdf::qwop;
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_ignore_std() {
+ // Even if you include a crate name, and use it in the doctest, we still won't include an
+ // `extern crate` statement if the crate is "std" -- that's included already by the
+ // compiler!
+ let opts = TestOptions::default();
+ let input = "use std::*;
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+fn main() {
+use std::*;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("std"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_manual_extern_crate() {
+ // When you manually include an `extern crate` statement in your doctest, `make_test`
+ // assumes you've included one for your own crate too.
+ let opts = TestOptions::default();
+ let input = "extern crate asdf;
+use asdf::qwop;
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_manual_extern_crate_with_macro_use() {
+ let opts = TestOptions::default();
+ let input = "#[macro_use] extern crate asdf;
+use asdf::qwop;
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+#[macro_use] extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_opts_attrs() {
+ // If you supplied some doctest attributes with `#![doc(test(attr(...)))]`, it will use
+ // those instead of the stock `#![allow(unused)]`.
+ let mut opts = TestOptions::default();
+ opts.attrs.push("feature(sick_rad)".to_string());
+ let input = "use asdf::qwop;
+assert_eq!(2+2, 4);";
+ let expected = "#![feature(sick_rad)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 3));
+
+ // Adding more will also bump the returned line offset.
+ opts.attrs.push("feature(hella_dope)".to_string());
+ let expected = "#![feature(sick_rad)]
+#![feature(hella_dope)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 4));
+}
+
+#[test]
+fn make_test_crate_attrs() {
+ // Including inner attributes in your doctest will apply them to the whole "crate", pasting
+ // them outside the generated main function.
+ let opts = TestOptions::default();
+ let input = "#![feature(sick_rad)]
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+#![feature(sick_rad)]
+fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_with_main() {
+ // Including your own `fn main` wrapper lets the test use it verbatim.
+ let opts = TestOptions::default();
+ let input = "fn main() {
+ assert_eq!(2+2, 4);
+}";
+ let expected = "#![allow(unused)]
+fn main() {
+ assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 1));
+}
+
+#[test]
+fn make_test_fake_main() {
+ // ... but putting it in a comment will still provide a wrapper.
+ let opts = TestOptions::default();
+ let input = "//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+//Ceci n'est pas une `fn main`
+fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+}
+
+#[test]
+fn make_test_dont_insert_main() {
+ // Even with that, if you set `dont_insert_main`, it won't create the `fn main` wrapper.
+ let opts = TestOptions::default();
+ let input = "//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);";
+ let expected = "#![allow(unused)]
+//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);"
+ .to_string();
+ let output = make_test(input, None, true, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 1));
+}
+
+#[test]
+fn make_test_display_warnings() {
+ // If the user is asking to display doctest warnings, suppress the default `allow(unused)`.
+ let mut opts = TestOptions::default();
+ opts.display_warnings = true;
+ let input = "assert_eq!(2+2, 4);";
+ let expected = "fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 1));
+}
+
+#[test]
+fn make_test_issues_21299_33731() {
+ let opts = TestOptions::default();
+
+ let input = "// fn main
+assert_eq!(2+2, 4);";
+
+ let expected = "#![allow(unused)]
+// fn main
+fn main() {
+assert_eq!(2+2, 4);
+}"
+ .to_string();
+
+ let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 2));
+
+ let input = "extern crate hella_qwop;
+assert_eq!(asdf::foo, 4);";
+
+ let expected = "#![allow(unused)]
+extern crate hella_qwop;
+extern crate asdf;
+fn main() {
+assert_eq!(asdf::foo, 4);
+}"
+ .to_string();
+
+ let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 3));
+}
+
+#[test]
+fn make_test_main_in_macro() {
+ let opts = TestOptions::default();
+ let input = "#[macro_use] extern crate my_crate;
+test_wrapper! {
+ fn main() {}
+}";
+ let expected = "#![allow(unused)]
+#[macro_use] extern crate my_crate;
+test_wrapper! {
+ fn main() {}
+}"
+ .to_string();
+
+ let output = make_test(input, Some("my_crate"), false, &opts, DEFAULT_EDITION);
+ assert_eq!(output, (expected, 1));
+}
use std::ops::Range;
use std::str;
+use crate::doctest;
use crate::html::highlight;
use crate::html::toc::TocBuilder;
-use crate::test;
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
.collect::<Vec<Cow<'_, str>>>()
.join("\n");
let krate = krate.as_ref().map(|s| &**s);
- let (test, _) = test::make_test(&test, krate, false, &Default::default(), edition);
+ let (test, _) = doctest::make_test(&test, krate, false, &Default::default(), edition);
let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" };
let edition_string = format!("&edition={}", edition);
}
}
-pub fn find_testable_code<T: test::Tester>(
+pub fn find_testable_code<T: doctest::Tester>(
doc: &str,
tests: &mut T,
error_codes: ErrorCodes,
krates
.iter()
.map(|s| {
- format!("<li><a href=\"{}index.html\">{}</li>", ensure_trailing_slash(s), s)
+ format!(
+ "<li><a class=\"mod\" href=\"{}index.html\">{}</a></li>",
+ ensure_trailing_slash(s),
+ s
+ )
})
.collect::<String>()
);
fn stability_tags(item: &clean::Item) -> String {
let mut tags = String::new();
- fn tag_html(class: &str, contents: &str) -> String {
- format!(r#"<span class="stab {}">{}</span>"#, class, contents)
+ fn tag_html(class: &str, title: &str, contents: &str) -> String {
+ format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
}
// The trailing space after each tag is to space it properly against the rest of the docs.
if !stability::deprecation_in_effect(depr.is_since_rustc_version, depr.since.as_deref()) {
message = "Deprecation planned";
}
- tags += &tag_html("deprecated", message);
+ tags += &tag_html("deprecated", "", message);
}
// The "rustc_private" crates are permanently unstable so it makes no sense
.map(|s| s.level == stability::Unstable && s.feature != "rustc_private")
== Some(true)
{
- tags += &tag_html("unstable", "Experimental");
+ tags += &tag_html("unstable", "", "Experimental");
}
if let Some(ref cfg) = item.attrs.cfg {
- tags += &tag_html("portability", &cfg.render_short_html());
+ tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
}
tags
}
function getHelpElement() {
+ buildHelperPopup();
return document.getElementById("help");
}
}
function showResults(results) {
- if (results.others.length === 1 &&
- getCurrentValue("rustdoc-go-to-only-result") === "true") {
+ var search = getSearchElement();
+ if (results.others.length === 1
+ && getCurrentValue("rustdoc-go-to-only-result") === "true"
+ // By default, the search DOM element is "empty" (meaning it has no children not
+ // text content). Once a search has been run, it won't be empty, even if you press
+ // ESC or empty the search input (which also "cancels" the search).
+ && (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText()))
+ {
var elem = document.createElement("a");
elem.href = results.others[0].href;
elem.style.display = "none";
// For firefox, we need the element to be in the DOM so it can be clicked.
document.body.appendChild(elem);
elem.click();
+ return;
}
var query = getQuery(search_input.value);
"</div><div id=\"results\">" +
ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>";
- var search = getSearchElement();
search.innerHTML = output;
showSearchResults(search);
var tds = search.getElementsByTagName("td");
}
}
+ function getSearchLoadingText() {
+ return "Loading search results...";
+ }
+
if (search_input) {
search_input.onfocus = function() {
putBackSearch(this);
var params = getQueryStringParams();
if (params && params.search) {
var search = getSearchElement();
- search.innerHTML = "<h3 style=\"text-align: center;\">Loading search results...</h3>";
+ search.innerHTML = "<h3 style=\"text-align: center;\">" + getSearchLoadingText() + "</h3>";
showSearchResults(search);
}
var infos = [
"Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
- restrict the search to a given type.",
- "Accepted types are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
+ restrict the search to a given item kind.",
+ "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
<code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
and <code>const</code>.",
"Search functions by type signature (e.g., <code>vec -> usize</code> or \
popup.appendChild(container);
insertAfter(popup, getSearchElement());
+ // So that it's only built once and then it'll do nothing when called!
+ buildHelperPopup = function() {};
}
onHashChange(null);
window.onhashchange = onHashChange;
-
- buildHelperPopup();
}());
// This is required in firefox. Explanations: when going back in the history, firefox doesn't re-run
#theme-choices > button {
border: none;
width: 100%;
- padding: 4px;
+ padding: 4px 8px;
text-align: center;
background: rgba(0,0,0,0);
}
#theme-picker, #settings-menu, .help-button {
border-color: #5c6773;
background-color: #0f1419;
+ color: #fff;
}
#theme-picker > img, #settings-menu > img {
}
#theme-choices > button:not(:first-child) {
- border-top-color: #c5c5c5;
+ border-top-color: #5c6773;
}
#theme-choices > button:hover, #theme-choices > button:focus {
#theme-picker, #settings-menu, .help-button {
border-color: #e0e0e0;
background: #f0f0f0;
+ color: #000;
}
#theme-picker:hover, #theme-picker:focus,
mod doctree;
#[macro_use]
mod error;
+mod doctest;
mod fold;
crate mod formats;
pub mod html;
mod json;
mod markdown;
mod passes;
-mod test;
mod theme;
mod visit_ast;
mod visit_lib;
}
fn main_options(options: config::Options) -> MainResult {
- let diag = core::new_handler(options.error_format, None, &options.debugging_options);
+ let diag = core::new_handler(options.error_format, None, &options.debugging_opts);
match (options.should_test, options.markdown_input()) {
(true, true) => return wrap_return(&diag, markdown::test(options)),
- (true, false) => return test::run(options),
+ (true, false) => return doctest::run(options),
(false, true) => {
return wrap_return(
&diag,
// need to move these items separately because we lose them by the time the closure is called,
// but we can't crates the Handler ahead of time because it's not Send
- let diag_opts = (options.error_format, options.edition, options.debugging_options.clone());
+ let diag_opts = (options.error_format, options.edition, options.debugging_opts.clone());
let show_coverage = options.show_coverage;
// First, parse the crate and extract all relevant information.
use rustc_span::source_map::DUMMY_SP;
use crate::config::{Options, RenderOptions};
+use crate::doctest::{Collector, TestOptions};
use crate::html::escape::Escape;
use crate::html::markdown;
use crate::html::markdown::{find_testable_code, ErrorCodes, IdMap, Markdown, MarkdownWithToc};
-use crate::test::{Collector, TestOptions};
/// Separate any lines at the start of the file that begin with `# ` or `%`.
fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
}
let candidates =
candidates.map(|candidate| candidate.map(|(res, _)| res));
- let candidates = [TypeNS, ValueNS, MacroNS]
- .iter()
- .filter_map(|&ns| candidates[ns].map(|res| (res, ns)));
ambiguity_error(
cx,
&item,
path_str,
&dox,
link_range,
- candidates.collect(),
+ candidates.present_items().collect(),
);
continue;
}
fragment = Some(path.to_owned());
} else {
// `[char]` when a `char` module is in scope
- let candidates = vec![(res, TypeNS), (prim, TypeNS)];
+ let candidates = vec![res, prim];
ambiguity_error(cx, &item, path_str, &dox, link_range, candidates);
continue;
}
specified.article(),
specified.descr()
);
- let suggestion = resolved.display_for(path_str);
- let help_msg =
- format!("to link to the {}, use its disambiguator", resolved.descr());
diag.note(¬e);
- if let Some(sp) = sp {
- diag.span_suggestion(
- sp,
- &help_msg,
- suggestion,
- Applicability::MaybeIncorrect,
- );
- } else {
- diag.help(&format!("{}: {}", help_msg, suggestion));
- }
+ suggest_disambiguator(resolved, diag, path_str, &dox, sp, &link_range);
});
};
if let Res::PrimTy(_) = res {
}
}
- fn display_for(self, path_str: &str) -> String {
+ /// WARNING: panics on `Res::Err`
+ fn from_res(res: Res) -> Self {
+ match res {
+ Res::Def(kind, _) => Disambiguator::Kind(kind),
+ Res::PrimTy(_) => Disambiguator::Primitive,
+ _ => Disambiguator::Namespace(res.ns().expect("can't call `from_res` on Res::err")),
+ }
+ }
+
+ /// Return (description of the change, suggestion)
+ fn display_for(self, path_str: &str) -> (&'static str, String) {
+ const PREFIX: &str = "prefix with the item kind";
+ const FUNCTION: &str = "add parentheses";
+ const MACRO: &str = "add an exclamation mark";
+
let kind = match self {
- Disambiguator::Primitive => return format!("prim@{}", path_str),
+ Disambiguator::Primitive => return (PREFIX, format!("prim@{}", path_str)),
Disambiguator::Kind(kind) => kind,
Disambiguator::Namespace(_) => panic!("display_for cannot be used on namespaces"),
};
if kind == DefKind::Macro(MacroKind::Bang) {
- return format!("{}!", path_str);
+ return (MACRO, format!("{}!", path_str));
} else if kind == DefKind::Fn || kind == DefKind::AssocFn {
- return format!("{}()", path_str);
+ return (FUNCTION, format!("{}()", path_str));
}
+
let prefix = match kind {
DefKind::Struct => "struct",
DefKind::Enum => "enum",
Namespace::MacroNS => "macro",
},
};
- format!("{}@{}", prefix, path_str)
+
+ // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
+ (PREFIX, format!("{}@{}", prefix, path_str))
}
fn ns(self) -> Namespace {
path_str: &str,
dox: &str,
link_range: Option<Range<usize>>,
- candidates: Vec<(Res, Namespace)>,
+ candidates: Vec<Res>,
) {
let mut msg = format!("`{}` is ", path_str);
match candidates.as_slice() {
- [(first_def, _), (second_def, _)] => {
+ [first_def, second_def] => {
msg += &format!(
"both {} {} and {} {}",
first_def.article(),
}
_ => {
let mut candidates = candidates.iter().peekable();
- while let Some((res, _)) = candidates.next() {
+ while let Some(res) = candidates.next() {
if candidates.peek().is_some() {
msg += &format!("{} {}, ", res.article(), res.descr());
} else {
report_diagnostic(cx, &msg, item, dox, link_range.clone(), |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
+ } else {
+ diag.note("ambiguous link");
+ }
- let link_range = link_range.expect("must have a link range if we have a span");
-
- for (res, ns) in candidates {
- let (action, mut suggestion) = match res {
- Res::Def(DefKind::AssocFn | DefKind::Fn, _) => {
- ("add parentheses", format!("{}()", path_str))
- }
- Res::Def(DefKind::Macro(MacroKind::Bang), _) => {
- ("add an exclamation mark", format!("{}!", path_str))
- }
- _ => {
- let type_ = match (res, ns) {
- (Res::PrimTy(_), _) => "prim",
- (Res::Def(DefKind::Const, _), _) => "const",
- (Res::Def(DefKind::Static, _), _) => "static",
- (Res::Def(DefKind::Struct, _), _) => "struct",
- (Res::Def(DefKind::Enum, _), _) => "enum",
- (Res::Def(DefKind::Union, _), _) => "union",
- (Res::Def(DefKind::Trait, _), _) => "trait",
- (Res::Def(DefKind::Mod, _), _) => "module",
- (_, TypeNS) => "type",
- (_, ValueNS) => "value",
- (Res::Def(DefKind::Macro(MacroKind::Derive), _), MacroNS) => "derive",
- (_, MacroNS) => "macro",
- };
-
- // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
- ("prefix with the item type", format!("{}@{}", type_, path_str))
- }
- };
+ for res in candidates {
+ let disambiguator = Disambiguator::from_res(res);
+ suggest_disambiguator(disambiguator, diag, path_str, dox, sp, &link_range);
+ }
+ });
+}
- if dox.bytes().nth(link_range.start) == Some(b'`') {
- suggestion = format!("`{}`", suggestion);
- }
+fn suggest_disambiguator(
+ disambiguator: Disambiguator,
+ diag: &mut DiagnosticBuilder<'_>,
+ path_str: &str,
+ dox: &str,
+ sp: Option<rustc_span::Span>,
+ link_range: &Option<Range<usize>>,
+) {
+ let (action, mut suggestion) = disambiguator.display_for(path_str);
+ let help = format!("to link to the {}, {}", disambiguator.descr(), action);
- // FIXME: Create a version of this suggestion for when we don't have the span.
- diag.span_suggestion(
- sp,
- &format!("to link to the {}, {}", res.descr(), action),
- suggestion,
- Applicability::MaybeIncorrect,
- );
- }
+ if let Some(sp) = sp {
+ let link_range = link_range.as_ref().expect("must have a link range if we have a span");
+ if dox.bytes().nth(link_range.start) == Some(b'`') {
+ suggestion = format!("`{}`", suggestion);
}
- });
+
+ diag.span_suggestion(sp, &help, suggestion, Applicability::MaybeIncorrect);
+ } else {
+ diag.help(&format!("{}: {}", help, suggestion));
+ }
}
fn privacy_error(
//! This pass is overloaded and runs two different lints.
//!
-//! - MISSING_DOC_CODE_EXAMPLES: this looks for public items missing doc-tests
-//! - PRIVATE_DOC_TESTS: this looks for private items with doc-tests.
+//! - MISSING_DOC_CODE_EXAMPLES: this lint is **UNSTABLE** and looks for public items missing doc-tests
+//! - PRIVATE_DOC_TESTS: this lint is **STABLE** and looks for private items with doc-tests.
use super::{span_of_attrs, Pass};
use crate::clean;
}
}
-impl crate::test::Tester for Tests {
+impl crate::doctest::Tester for Tests {
fn add_test(&mut self, _: String, _: LangString, _: usize) {
self.found_tests += 1;
}
find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None);
- if tests.found_tests == 0 {
+ if tests.found_tests == 0
+ && rustc_feature::UnstableFeatures::from_environment().is_nightly_build()
+ {
if should_have_doc_example(&item.inner) {
debug!("reporting error for {:?} (hir_id={:?})", item, hir_id);
let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
|lint| lint.build("missing code example in this documentation").emit(),
);
}
- } else if rustc_feature::UnstableFeatures::from_environment().is_nightly_build()
- && tests.found_tests > 0
- && !cx.renderinfo.borrow().access_levels.is_public(item.def_id)
+ } else if tests.found_tests > 0 && !cx.renderinfo.borrow().access_levels.is_public(item.def_id)
{
cx.tcx.struct_span_lint_hir(
lint::builtin::PRIVATE_DOC_TESTS,
+++ /dev/null
-use rustc_ast as ast;
-use rustc_data_structures::sync::Lrc;
-use rustc_errors::ErrorReported;
-use rustc_feature::UnstableFeatures;
-use rustc_hir as hir;
-use rustc_hir::intravisit;
-use rustc_hir::{HirId, CRATE_HIR_ID};
-use rustc_interface::interface;
-use rustc_middle::hir::map::Map;
-use rustc_middle::ty::TyCtxt;
-use rustc_session::config::{self, CrateType};
-use rustc_session::{lint, DiagnosticOutput, Session};
-use rustc_span::edition::Edition;
-use rustc_span::source_map::SourceMap;
-use rustc_span::symbol::sym;
-use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
-use rustc_target::spec::TargetTriple;
-use tempfile::Builder as TempFileBuilder;
-
-use std::collections::HashMap;
-use std::env;
-use std::io::{self, Write};
-use std::panic;
-use std::path::PathBuf;
-use std::process::{self, Command, Stdio};
-use std::str;
-
-use crate::clean::Attributes;
-use crate::config::Options;
-use crate::core::init_lints;
-use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
-use crate::passes::span_of_attrs;
-
-#[derive(Clone, Default)]
-pub struct TestOptions {
- /// Whether to disable the default `extern crate my_crate;` when creating doctests.
- pub no_crate_inject: bool,
- /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
- /// the default `#![allow(unused)]`.
- pub display_warnings: bool,
- /// Additional crate-level attributes to add to doctests.
- pub attrs: Vec<String>,
-}
-
-pub fn run(options: Options) -> Result<(), ErrorReported> {
- let input = config::Input::File(options.input.clone());
-
- let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
-
- // In addition to those specific lints, we also need to allow those given through
- // command line, otherwise they'll get ignored and we don't want that.
- let allowed_lints = vec![invalid_codeblock_attributes_name.to_owned()];
-
- let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| {
- if lint.name == invalid_codeblock_attributes_name {
- None
- } else {
- Some((lint.name_lower(), lint::Allow))
- }
- });
-
- let crate_types =
- if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
-
- let sessopts = config::Options {
- maybe_sysroot: options.maybe_sysroot.clone(),
- search_paths: options.libs.clone(),
- crate_types,
- lint_opts: if !options.display_warnings { lint_opts } else { vec![] },
- lint_cap: Some(options.lint_cap.clone().unwrap_or_else(|| lint::Forbid)),
- cg: options.codegen_options.clone(),
- externs: options.externs.clone(),
- unstable_features: UnstableFeatures::from_environment(),
- actually_rustdoc: true,
- debugging_opts: config::DebuggingOptions { ..config::basic_debugging_options() },
- edition: options.edition,
- target_triple: options.target.clone(),
- ..config::Options::default()
- };
-
- let mut cfgs = options.cfgs.clone();
- cfgs.push("doc".to_owned());
- cfgs.push("doctest".to_owned());
- let config = interface::Config {
- opts: sessopts,
- crate_cfg: interface::parse_cfgspecs(cfgs),
- input,
- input_path: None,
- output_file: None,
- output_dir: None,
- file_loader: None,
- diagnostic_output: DiagnosticOutput::Default,
- stderr: None,
- crate_name: options.crate_name.clone(),
- lint_caps,
- register_lints: None,
- override_queries: None,
- registry: rustc_driver::diagnostics_registry(),
- };
-
- let mut test_args = options.test_args.clone();
- let display_warnings = options.display_warnings;
-
- let tests = interface::run_compiler(config, |compiler| {
- compiler.enter(|queries| {
- let lower_to_hir = queries.lower_to_hir()?;
-
- let mut opts = scrape_test_config(lower_to_hir.peek().0);
- opts.display_warnings |= options.display_warnings;
- let enable_per_target_ignores = options.enable_per_target_ignores;
- let mut collector = Collector::new(
- queries.crate_name()?.peek().to_string(),
- options,
- false,
- opts,
- Some(compiler.session().parse_sess.clone_source_map()),
- None,
- enable_per_target_ignores,
- );
-
- let mut global_ctxt = queries.global_ctxt()?.take();
-
- global_ctxt.enter(|tcx| {
- let krate = tcx.hir().krate();
-
- let mut hir_collector = HirCollector {
- sess: compiler.session(),
- collector: &mut collector,
- map: tcx.hir(),
- codes: ErrorCodes::from(
- compiler.session().opts.unstable_features.is_nightly_build(),
- ),
- tcx,
- };
- hir_collector.visit_testable(
- "".to_string(),
- &krate.item.attrs,
- CRATE_HIR_ID,
- krate.item.span,
- |this| {
- intravisit::walk_crate(this, krate);
- },
- );
- });
- compiler.session().abort_if_errors();
-
- let ret: Result<_, ErrorReported> = Ok(collector.tests);
- ret
- })
- });
- let tests = match tests {
- Ok(tests) => tests,
- Err(ErrorReported) => return Err(ErrorReported),
- };
-
- test_args.insert(0, "rustdoctest".to_string());
-
- testing::test_main(
- &test_args,
- tests,
- Some(testing::Options::new().display_output(display_warnings)),
- );
-
- Ok(())
-}
-
-// Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
-fn scrape_test_config(krate: &::rustc_hir::Crate<'_>) -> TestOptions {
- use rustc_ast_pretty::pprust;
-
- let mut opts =
- TestOptions { no_crate_inject: false, display_warnings: false, attrs: Vec::new() };
-
- let test_attrs: Vec<_> = krate
- .item
- .attrs
- .iter()
- .filter(|a| a.has_name(sym::doc))
- .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
- .filter(|a| a.has_name(sym::test))
- .collect();
- let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
-
- for attr in attrs {
- if attr.has_name(sym::no_crate_inject) {
- opts.no_crate_inject = true;
- }
- if attr.has_name(sym::attr) {
- if let Some(l) = attr.meta_item_list() {
- for item in l {
- opts.attrs.push(pprust::meta_list_item_to_string(item));
- }
- }
- }
- }
-
- opts
-}
-
-/// Documentation test failure modes.
-enum TestFailure {
- /// The test failed to compile.
- CompileError,
- /// The test is marked `compile_fail` but compiled successfully.
- UnexpectedCompilePass,
- /// The test failed to compile (as expected) but the compiler output did not contain all
- /// expected error codes.
- MissingErrorCodes(Vec<String>),
- /// The test binary was unable to be executed.
- ExecutionError(io::Error),
- /// The test binary exited with a non-zero exit code.
- ///
- /// This typically means an assertion in the test failed or another form of panic occurred.
- ExecutionFailure(process::Output),
- /// The test is marked `should_panic` but the test binary executed successfully.
- UnexpectedRunPass,
-}
-
-enum DirState {
- Temp(tempfile::TempDir),
- Perm(PathBuf),
-}
-
-impl DirState {
- fn path(&self) -> &std::path::Path {
- match self {
- DirState::Temp(t) => t.path(),
- DirState::Perm(p) => p.as_path(),
- }
- }
-}
-
-fn run_test(
- test: &str,
- cratename: &str,
- line: usize,
- options: Options,
- should_panic: bool,
- no_run: bool,
- as_test_harness: bool,
- runtool: Option<String>,
- runtool_args: Vec<String>,
- target: TargetTriple,
- compile_fail: bool,
- mut error_codes: Vec<String>,
- opts: &TestOptions,
- edition: Edition,
- outdir: DirState,
- path: PathBuf,
-) -> Result<(), TestFailure> {
- let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts, edition);
-
- let output_file = outdir.path().join("rust_out");
-
- let rustc_binary = options
- .test_builder
- .as_deref()
- .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
- let mut compiler = Command::new(&rustc_binary);
- compiler.arg("--crate-type").arg("bin");
- for cfg in &options.cfgs {
- compiler.arg("--cfg").arg(&cfg);
- }
- if let Some(sysroot) = options.maybe_sysroot {
- compiler.arg("--sysroot").arg(sysroot);
- }
- compiler.arg("--edition").arg(&edition.to_string());
- compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
- compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
- compiler.arg("-o").arg(&output_file);
- if as_test_harness {
- compiler.arg("--test");
- }
- for lib_str in &options.lib_strs {
- compiler.arg("-L").arg(&lib_str);
- }
- for extern_str in &options.extern_strs {
- compiler.arg("--extern").arg(&extern_str);
- }
- compiler.arg("-Ccodegen-units=1");
- for codegen_options_str in &options.codegen_options_strs {
- compiler.arg("-C").arg(&codegen_options_str);
- }
- for debugging_option_str in &options.debugging_options_strs {
- compiler.arg("-Z").arg(&debugging_option_str);
- }
- if no_run && !compile_fail {
- compiler.arg("--emit=metadata");
- }
- compiler.arg("--target").arg(match target {
- TargetTriple::TargetTriple(s) => s,
- TargetTriple::TargetPath(path) => {
- path.to_str().expect("target path must be valid unicode").to_string()
- }
- });
-
- compiler.arg("-");
- compiler.stdin(Stdio::piped());
- compiler.stderr(Stdio::piped());
-
- let mut child = compiler.spawn().expect("Failed to spawn rustc process");
- {
- let stdin = child.stdin.as_mut().expect("Failed to open stdin");
- stdin.write_all(test.as_bytes()).expect("could write out test sources");
- }
- let output = child.wait_with_output().expect("Failed to read stdout");
-
- struct Bomb<'a>(&'a str);
- impl Drop for Bomb<'_> {
- fn drop(&mut self) {
- eprint!("{}", self.0);
- }
- }
- let out = str::from_utf8(&output.stderr).unwrap();
- let _bomb = Bomb(&out);
- match (output.status.success(), compile_fail) {
- (true, true) => {
- return Err(TestFailure::UnexpectedCompilePass);
- }
- (true, false) => {}
- (false, true) => {
- if !error_codes.is_empty() {
- error_codes.retain(|err| !out.contains(&format!("error[{}]: ", err)));
-
- if !error_codes.is_empty() {
- return Err(TestFailure::MissingErrorCodes(error_codes));
- }
- }
- }
- (false, false) => {
- return Err(TestFailure::CompileError);
- }
- }
-
- if no_run {
- return Ok(());
- }
-
- // Run the code!
- let mut cmd;
-
- if let Some(tool) = runtool {
- cmd = Command::new(tool);
- cmd.args(runtool_args);
- cmd.arg(output_file);
- } else {
- cmd = Command::new(output_file);
- }
-
- match cmd.output() {
- Err(e) => return Err(TestFailure::ExecutionError(e)),
- Ok(out) => {
- if should_panic && out.status.success() {
- return Err(TestFailure::UnexpectedRunPass);
- } else if !should_panic && !out.status.success() {
- return Err(TestFailure::ExecutionFailure(out));
- }
- }
- }
-
- Ok(())
-}
-
-/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
-/// lines before the test code begins.
-pub fn make_test(
- s: &str,
- cratename: Option<&str>,
- dont_insert_main: bool,
- opts: &TestOptions,
- edition: Edition,
-) -> (String, usize) {
- let (crate_attrs, everything_else, crates) = partition_source(s);
- let everything_else = everything_else.trim();
- let mut line_offset = 0;
- let mut prog = String::new();
-
- if opts.attrs.is_empty() && !opts.display_warnings {
- // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
- // lints that are commonly triggered in doctests. The crate-level test attributes are
- // commonly used to make tests fail in case they trigger warnings, so having this there in
- // that case may cause some tests to pass when they shouldn't have.
- prog.push_str("#![allow(unused)]\n");
- line_offset += 1;
- }
-
- // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
- for attr in &opts.attrs {
- prog.push_str(&format!("#![{}]\n", attr));
- line_offset += 1;
- }
-
- // Now push any outer attributes from the example, assuming they
- // are intended to be crate attributes.
- prog.push_str(&crate_attrs);
- prog.push_str(&crates);
-
- // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
- // crate already is included.
- let result = rustc_driver::catch_fatal_errors(|| {
- rustc_span::with_session_globals(edition, || {
- use rustc_errors::emitter::EmitterWriter;
- use rustc_errors::Handler;
- use rustc_parse::maybe_new_parser_from_source_str;
- use rustc_session::parse::ParseSess;
- use rustc_span::source_map::FilePathMapping;
-
- let filename = FileName::anon_source_code(s);
- let source = crates + everything_else;
-
- // Any errors in parsing should also appear when the doctest is compiled for real, so just
- // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
- let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
- let emitter =
- EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
- // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
- let handler = Handler::with_emitter(false, None, box emitter);
- let sess = ParseSess::with_span_handler(handler, sm);
-
- let mut found_main = false;
- let mut found_extern_crate = cratename.is_none();
- let mut found_macro = false;
-
- let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
- Ok(p) => p,
- Err(errs) => {
- for mut err in errs {
- err.cancel();
- }
-
- return (found_main, found_extern_crate, found_macro);
- }
- };
-
- loop {
- match parser.parse_item() {
- Ok(Some(item)) => {
- if !found_main {
- if let ast::ItemKind::Fn(..) = item.kind {
- if item.ident.name == sym::main {
- found_main = true;
- }
- }
- }
-
- if !found_extern_crate {
- if let ast::ItemKind::ExternCrate(original) = item.kind {
- // This code will never be reached if `cratename` is none because
- // `found_extern_crate` is initialized to `true` if it is none.
- let cratename = cratename.unwrap();
-
- match original {
- Some(name) => found_extern_crate = name.as_str() == cratename,
- None => found_extern_crate = item.ident.as_str() == cratename,
- }
- }
- }
-
- if !found_macro {
- if let ast::ItemKind::MacCall(..) = item.kind {
- found_macro = true;
- }
- }
-
- if found_main && found_extern_crate {
- break;
- }
- }
- Ok(None) => break,
- Err(mut e) => {
- e.cancel();
- break;
- }
- }
- }
-
- (found_main, found_extern_crate, found_macro)
- })
- });
- let (already_has_main, already_has_extern_crate, found_macro) = match result {
- Ok(result) => result,
- Err(ErrorReported) => {
- // If the parser panicked due to a fatal error, pass the test code through unchanged.
- // The error will be reported during compilation.
- return (s.to_owned(), 0);
- }
- };
-
- // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
- // see it. In that case, run the old text-based scan to see if they at least have a main
- // function written inside a macro invocation. See
- // https://github.com/rust-lang/rust/issues/56898
- let already_has_main = if found_macro && !already_has_main {
- s.lines()
- .map(|line| {
- let comment = line.find("//");
- if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
- })
- .any(|code| code.contains("fn main"))
- } else {
- already_has_main
- };
-
- // Don't inject `extern crate std` because it's already injected by the
- // compiler.
- if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
- if let Some(cratename) = cratename {
- // Make sure its actually used if not included.
- if s.contains(cratename) {
- prog.push_str(&format!("extern crate {};\n", cratename));
- line_offset += 1;
- }
- }
- }
-
- // FIXME: This code cannot yet handle no_std test cases yet
- if dont_insert_main || already_has_main || prog.contains("![no_std]") {
- prog.push_str(everything_else);
- } else {
- let returns_result = everything_else.trim_end().ends_with("(())");
- let (main_pre, main_post) = if returns_result {
- (
- "fn main() { fn _inner() -> Result<(), impl core::fmt::Debug> {",
- "}\n_inner().unwrap() }",
- )
- } else {
- ("fn main() {\n", "\n}")
- };
- prog.extend([main_pre, everything_else, main_post].iter().cloned());
- line_offset += 1;
- }
-
- debug!("final doctest:\n{}", prog);
-
- (prog, line_offset)
-}
-
-// FIXME(aburka): use a real parser to deal with multiline attributes
-fn partition_source(s: &str) -> (String, String, String) {
- #[derive(Copy, Clone, PartialEq)]
- enum PartitionState {
- Attrs,
- Crates,
- Other,
- }
- let mut state = PartitionState::Attrs;
- let mut before = String::new();
- let mut crates = String::new();
- let mut after = String::new();
-
- for line in s.lines() {
- let trimline = line.trim();
-
- // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
- // shunted into "everything else"
- match state {
- PartitionState::Attrs => {
- state = if trimline.starts_with("#![")
- || trimline.chars().all(|c| c.is_whitespace())
- || (trimline.starts_with("//") && !trimline.starts_with("///"))
- {
- PartitionState::Attrs
- } else if trimline.starts_with("extern crate")
- || trimline.starts_with("#[macro_use] extern crate")
- {
- PartitionState::Crates
- } else {
- PartitionState::Other
- };
- }
- PartitionState::Crates => {
- state = if trimline.starts_with("extern crate")
- || trimline.starts_with("#[macro_use] extern crate")
- || trimline.chars().all(|c| c.is_whitespace())
- || (trimline.starts_with("//") && !trimline.starts_with("///"))
- {
- PartitionState::Crates
- } else {
- PartitionState::Other
- };
- }
- PartitionState::Other => {}
- }
-
- match state {
- PartitionState::Attrs => {
- before.push_str(line);
- before.push_str("\n");
- }
- PartitionState::Crates => {
- crates.push_str(line);
- crates.push_str("\n");
- }
- PartitionState::Other => {
- after.push_str(line);
- after.push_str("\n");
- }
- }
- }
-
- debug!("before:\n{}", before);
- debug!("crates:\n{}", crates);
- debug!("after:\n{}", after);
-
- (before, after, crates)
-}
-
-pub trait Tester {
- fn add_test(&mut self, test: String, config: LangString, line: usize);
- fn get_line(&self) -> usize {
- 0
- }
- fn register_header(&mut self, _name: &str, _level: u32) {}
-}
-
-pub struct Collector {
- pub tests: Vec<testing::TestDescAndFn>,
-
- // The name of the test displayed to the user, separated by `::`.
- //
- // In tests from Rust source, this is the path to the item
- // e.g., `["std", "vec", "Vec", "push"]`.
- //
- // In tests from a markdown file, this is the titles of all headers (h1~h6)
- // of the sections that contain the code block, e.g., if the markdown file is
- // written as:
- //
- // ``````markdown
- // # Title
- //
- // ## Subtitle
- //
- // ```rust
- // assert!(true);
- // ```
- // ``````
- //
- // the `names` vector of that test will be `["Title", "Subtitle"]`.
- names: Vec<String>,
-
- options: Options,
- use_headers: bool,
- enable_per_target_ignores: bool,
- cratename: String,
- opts: TestOptions,
- position: Span,
- source_map: Option<Lrc<SourceMap>>,
- filename: Option<PathBuf>,
- visited_tests: HashMap<(String, usize), usize>,
-}
-
-impl Collector {
- pub fn new(
- cratename: String,
- options: Options,
- use_headers: bool,
- opts: TestOptions,
- source_map: Option<Lrc<SourceMap>>,
- filename: Option<PathBuf>,
- enable_per_target_ignores: bool,
- ) -> Collector {
- Collector {
- tests: Vec::new(),
- names: Vec::new(),
- options,
- use_headers,
- enable_per_target_ignores,
- cratename,
- opts,
- position: DUMMY_SP,
- source_map,
- filename,
- visited_tests: HashMap::new(),
- }
- }
-
- fn generate_name(&self, line: usize, filename: &FileName) -> String {
- let mut item_path = self.names.join("::");
- if !item_path.is_empty() {
- item_path.push(' ');
- }
- format!("{} - {}(line {})", filename, item_path, line)
- }
-
- pub fn set_position(&mut self, position: Span) {
- self.position = position;
- }
-
- fn get_filename(&self) -> FileName {
- if let Some(ref source_map) = self.source_map {
- let filename = source_map.span_to_filename(self.position);
- if let FileName::Real(ref filename) = filename {
- if let Ok(cur_dir) = env::current_dir() {
- if let Ok(path) = filename.local_path().strip_prefix(&cur_dir) {
- return path.to_owned().into();
- }
- }
- }
- filename
- } else if let Some(ref filename) = self.filename {
- filename.clone().into()
- } else {
- FileName::Custom("input".to_owned())
- }
- }
-}
-
-impl Tester for Collector {
- fn add_test(&mut self, test: String, config: LangString, line: usize) {
- let filename = self.get_filename();
- let name = self.generate_name(line, &filename);
- let cratename = self.cratename.to_string();
- let opts = self.opts.clone();
- let edition = config.edition.unwrap_or(self.options.edition);
- let options = self.options.clone();
- let runtool = self.options.runtool.clone();
- let runtool_args = self.options.runtool_args.clone();
- let target = self.options.target.clone();
- let target_str = target.to_string();
-
- // FIXME(#44940): if doctests ever support path remapping, then this filename
- // needs to be the result of `SourceMap::span_to_unmapped_path`.
- let path = match &filename {
- FileName::Real(path) => path.local_path().to_path_buf(),
- _ => PathBuf::from(r"doctest.rs"),
- };
-
- let outdir = if let Some(mut path) = options.persist_doctests.clone() {
- // For example `module/file.rs` would become `module_file_rs`
- let folder_name = filename
- .to_string()
- .chars()
- .map(|c| if c == '/' || c == '.' { '_' } else { c })
- .collect::<String>();
-
- path.push(format!(
- "{name}_{line}_{number}",
- name = folder_name,
- number = {
- // Increases the current test number, if this file already
- // exists or it creates a new entry with a test number of 0.
- self.visited_tests
- .entry((folder_name.clone(), line))
- .and_modify(|v| *v += 1)
- .or_insert(0)
- },
- line = line,
- ));
-
- std::fs::create_dir_all(&path)
- .expect("Couldn't create directory for doctest executables");
-
- DirState::Perm(path)
- } else {
- DirState::Temp(
- TempFileBuilder::new()
- .prefix("rustdoctest")
- .tempdir()
- .expect("rustdoc needs a tempdir"),
- )
- };
-
- debug!("creating test {}: {}", name, test);
- self.tests.push(testing::TestDescAndFn {
- desc: testing::TestDesc {
- name: testing::DynTestName(name),
- ignore: match config.ignore {
- Ignore::All => true,
- Ignore::None => false,
- Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
- },
- // compiler failures are test failures
- should_panic: testing::ShouldPanic::No,
- allow_fail: config.allow_fail,
- test_type: testing::TestType::DocTest,
- },
- testfn: testing::DynTestFn(box move || {
- let res = run_test(
- &test,
- &cratename,
- line,
- options,
- config.should_panic,
- config.no_run,
- config.test_harness,
- runtool,
- runtool_args,
- target,
- config.compile_fail,
- config.error_codes,
- &opts,
- edition,
- outdir,
- path,
- );
-
- if let Err(err) = res {
- match err {
- TestFailure::CompileError => {
- eprint!("Couldn't compile the test.");
- }
- TestFailure::UnexpectedCompilePass => {
- eprint!("Test compiled successfully, but it's marked `compile_fail`.");
- }
- TestFailure::UnexpectedRunPass => {
- eprint!("Test executable succeeded, but it's marked `should_panic`.");
- }
- TestFailure::MissingErrorCodes(codes) => {
- eprint!("Some expected error codes were not found: {:?}", codes);
- }
- TestFailure::ExecutionError(err) => {
- eprint!("Couldn't run the test: {}", err);
- if err.kind() == io::ErrorKind::PermissionDenied {
- eprint!(" - maybe your tempdir is mounted with noexec?");
- }
- }
- TestFailure::ExecutionFailure(out) => {
- let reason = if let Some(code) = out.status.code() {
- format!("exit code {}", code)
- } else {
- String::from("terminated by signal")
- };
-
- eprintln!("Test executable failed ({}).", reason);
-
- // FIXME(#12309): An unfortunate side-effect of capturing the test
- // executable's output is that the relative ordering between the test's
- // stdout and stderr is lost. However, this is better than the
- // alternative: if the test executable inherited the parent's I/O
- // handles the output wouldn't be captured at all, even on success.
- //
- // The ordering could be preserved if the test process' stderr was
- // redirected to stdout, but that functionality does not exist in the
- // standard library, so it may not be portable enough.
- let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
- let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
-
- if !stdout.is_empty() || !stderr.is_empty() {
- eprintln!();
-
- if !stdout.is_empty() {
- eprintln!("stdout:\n{}", stdout);
- }
-
- if !stderr.is_empty() {
- eprintln!("stderr:\n{}", stderr);
- }
- }
- }
- }
-
- panic::resume_unwind(box ());
- }
- }),
- });
- }
-
- fn get_line(&self) -> usize {
- if let Some(ref source_map) = self.source_map {
- let line = self.position.lo().to_usize();
- let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
- if line > 0 { line - 1 } else { line }
- } else {
- 0
- }
- }
-
- fn register_header(&mut self, name: &str, level: u32) {
- if self.use_headers {
- // We use these headings as test names, so it's good if
- // they're valid identifiers.
- let name = name
- .chars()
- .enumerate()
- .map(|(i, c)| {
- if (i == 0 && rustc_lexer::is_id_start(c))
- || (i != 0 && rustc_lexer::is_id_continue(c))
- {
- c
- } else {
- '_'
- }
- })
- .collect::<String>();
-
- // Here we try to efficiently assemble the header titles into the
- // test name in the form of `h1::h2::h3::h4::h5::h6`.
- //
- // Suppose that originally `self.names` contains `[h1, h2, h3]`...
- let level = level as usize;
- if level <= self.names.len() {
- // ... Consider `level == 2`. All headers in the lower levels
- // are irrelevant in this new level. So we should reset
- // `self.names` to contain headers until <h2>, and replace that
- // slot with the new name: `[h1, name]`.
- self.names.truncate(level);
- self.names[level - 1] = name;
- } else {
- // ... On the other hand, consider `level == 5`. This means we
- // need to extend `self.names` to contain five headers. We fill
- // in the missing level (<h4>) with `_`. Thus `self.names` will
- // become `[h1, h2, h3, "_", name]`.
- if level - 1 > self.names.len() {
- self.names.resize(level - 1, "_".to_owned());
- }
- self.names.push(name);
- }
- }
- }
-}
-
-struct HirCollector<'a, 'hir, 'tcx> {
- sess: &'a Session,
- collector: &'a mut Collector,
- map: Map<'hir>,
- codes: ErrorCodes,
- tcx: TyCtxt<'tcx>,
-}
-
-impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
- fn visit_testable<F: FnOnce(&mut Self)>(
- &mut self,
- name: String,
- attrs: &[ast::Attribute],
- hir_id: HirId,
- sp: Span,
- nested: F,
- ) {
- let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
- if let Some(ref cfg) = attrs.cfg {
- if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
- return;
- }
- }
-
- let has_name = !name.is_empty();
- if has_name {
- self.collector.names.push(name);
- }
-
- attrs.collapse_doc_comments();
- attrs.unindent_doc_comments();
- // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
- // anything else, this will combine them for us.
- if let Some(doc) = attrs.collapsed_doc_value() {
- // Use the outermost invocation, so that doctest names come from where the docs were written.
- let span = attrs
- .span
- .map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
- .unwrap_or(DUMMY_SP);
- self.collector.set_position(span);
- markdown::find_testable_code(
- &doc,
- self.collector,
- self.codes,
- self.collector.enable_per_target_ignores,
- Some(&crate::html::markdown::ExtraInfo::new(
- &self.tcx,
- hir_id,
- span_of_attrs(&attrs).unwrap_or(sp),
- )),
- );
- }
-
- nested(self);
-
- if has_name {
- self.collector.names.pop();
- }
- }
-}
-
-impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> {
- type Map = Map<'hir>;
-
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::All(self.map)
- }
-
- fn visit_item(&mut self, item: &'hir hir::Item<'_>) {
- let name = if let hir::ItemKind::Impl { ref self_ty, .. } = item.kind {
- rustc_hir_pretty::id_to_string(&self.map, self_ty.hir_id)
- } else {
- item.ident.to_string()
- };
-
- self.visit_testable(name, &item.attrs, item.hir_id, item.span, |this| {
- intravisit::walk_item(this, item);
- });
- }
-
- fn visit_trait_item(&mut self, item: &'hir hir::TraitItem<'_>) {
- self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
- intravisit::walk_trait_item(this, item);
- });
- }
-
- fn visit_impl_item(&mut self, item: &'hir hir::ImplItem<'_>) {
- self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
- intravisit::walk_impl_item(this, item);
- });
- }
-
- fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem<'_>) {
- self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| {
- intravisit::walk_foreign_item(this, item);
- });
- }
-
- fn visit_variant(
- &mut self,
- v: &'hir hir::Variant<'_>,
- g: &'hir hir::Generics<'_>,
- item_id: hir::HirId,
- ) {
- self.visit_testable(v.ident.to_string(), &v.attrs, v.id, v.span, |this| {
- intravisit::walk_variant(this, v, g, item_id);
- });
- }
-
- fn visit_struct_field(&mut self, f: &'hir hir::StructField<'_>) {
- self.visit_testable(f.ident.to_string(), &f.attrs, f.hir_id, f.span, |this| {
- intravisit::walk_struct_field(this, f);
- });
- }
-
- fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef<'_>) {
- self.visit_testable(
- macro_def.ident.to_string(),
- ¯o_def.attrs,
- macro_def.hir_id,
- macro_def.span,
- |_| (),
- );
- }
-}
-
-#[cfg(test)]
-mod tests;
+++ /dev/null
-use super::{make_test, TestOptions};
-use rustc_span::edition::DEFAULT_EDITION;
-
-#[test]
-fn make_test_basic() {
- //basic use: wraps with `fn main`, adds `#![allow(unused)]`
- let opts = TestOptions::default();
- let input = "assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_crate_name_no_use() {
- // If you give a crate name but *don't* use it within the test, it won't bother inserting
- // the `extern crate` statement.
- let opts = TestOptions::default();
- let input = "assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_crate_name() {
- // If you give a crate name and use it within the test, it will insert an `extern crate`
- // statement before `fn main`.
- let opts = TestOptions::default();
- let input = "use asdf::qwop;
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-extern crate asdf;
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 3));
-}
-
-#[test]
-fn make_test_no_crate_inject() {
- // Even if you do use the crate within the test, setting `opts.no_crate_inject` will skip
- // adding it anyway.
- let opts = TestOptions { no_crate_inject: true, display_warnings: false, attrs: vec![] };
- let input = "use asdf::qwop;
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_ignore_std() {
- // Even if you include a crate name, and use it in the doctest, we still won't include an
- // `extern crate` statement if the crate is "std" -- that's included already by the
- // compiler!
- let opts = TestOptions::default();
- let input = "use std::*;
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-fn main() {
-use std::*;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("std"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_manual_extern_crate() {
- // When you manually include an `extern crate` statement in your doctest, `make_test`
- // assumes you've included one for your own crate too.
- let opts = TestOptions::default();
- let input = "extern crate asdf;
-use asdf::qwop;
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-extern crate asdf;
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_manual_extern_crate_with_macro_use() {
- let opts = TestOptions::default();
- let input = "#[macro_use] extern crate asdf;
-use asdf::qwop;
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-#[macro_use] extern crate asdf;
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_opts_attrs() {
- // If you supplied some doctest attributes with `#![doc(test(attr(...)))]`, it will use
- // those instead of the stock `#![allow(unused)]`.
- let mut opts = TestOptions::default();
- opts.attrs.push("feature(sick_rad)".to_string());
- let input = "use asdf::qwop;
-assert_eq!(2+2, 4);";
- let expected = "#![feature(sick_rad)]
-extern crate asdf;
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 3));
-
- // Adding more will also bump the returned line offset.
- opts.attrs.push("feature(hella_dope)".to_string());
- let expected = "#![feature(sick_rad)]
-#![feature(hella_dope)]
-extern crate asdf;
-fn main() {
-use asdf::qwop;
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 4));
-}
-
-#[test]
-fn make_test_crate_attrs() {
- // Including inner attributes in your doctest will apply them to the whole "crate", pasting
- // them outside the generated main function.
- let opts = TestOptions::default();
- let input = "#![feature(sick_rad)]
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-#![feature(sick_rad)]
-fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_with_main() {
- // Including your own `fn main` wrapper lets the test use it verbatim.
- let opts = TestOptions::default();
- let input = "fn main() {
- assert_eq!(2+2, 4);
-}";
- let expected = "#![allow(unused)]
-fn main() {
- assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 1));
-}
-
-#[test]
-fn make_test_fake_main() {
- // ... but putting it in a comment will still provide a wrapper.
- let opts = TestOptions::default();
- let input = "//Ceci n'est pas une `fn main`
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-//Ceci n'est pas une `fn main`
-fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-}
-
-#[test]
-fn make_test_dont_insert_main() {
- // Even with that, if you set `dont_insert_main`, it won't create the `fn main` wrapper.
- let opts = TestOptions::default();
- let input = "//Ceci n'est pas une `fn main`
-assert_eq!(2+2, 4);";
- let expected = "#![allow(unused)]
-//Ceci n'est pas une `fn main`
-assert_eq!(2+2, 4);"
- .to_string();
- let output = make_test(input, None, true, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 1));
-}
-
-#[test]
-fn make_test_display_warnings() {
- // If the user is asking to display doctest warnings, suppress the default `allow(unused)`.
- let mut opts = TestOptions::default();
- opts.display_warnings = true;
- let input = "assert_eq!(2+2, 4);";
- let expected = "fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 1));
-}
-
-#[test]
-fn make_test_issues_21299_33731() {
- let opts = TestOptions::default();
-
- let input = "// fn main
-assert_eq!(2+2, 4);";
-
- let expected = "#![allow(unused)]
-// fn main
-fn main() {
-assert_eq!(2+2, 4);
-}"
- .to_string();
-
- let output = make_test(input, None, false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 2));
-
- let input = "extern crate hella_qwop;
-assert_eq!(asdf::foo, 4);";
-
- let expected = "#![allow(unused)]
-extern crate hella_qwop;
-extern crate asdf;
-fn main() {
-assert_eq!(asdf::foo, 4);
-}"
- .to_string();
-
- let output = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 3));
-}
-
-#[test]
-fn make_test_main_in_macro() {
- let opts = TestOptions::default();
- let input = "#[macro_use] extern crate my_crate;
-test_wrapper! {
- fn main() {}
-}";
- let expected = "#![allow(unused)]
-#[macro_use] extern crate my_crate;
-test_wrapper! {
- fn main() {}
-}"
- .to_string();
-
- let output = make_test(input, Some("my_crate"), false, &opts, DEFAULT_EDITION);
- assert_eq!(output, (expected, 1));
-}
# source tarball for a stable release you'll likely see `1.x.0` for rustc and
# `0.(x+1).0` for Cargo where they were released on `date`.
-date: 2020-07-16
+date: 2020-08-26
rustc: beta
cargo: beta
asm!("adr x0, {}", sym extern_static);
}
+// Regression test for #75761
+// CHECK-LABEL: issue_75761:
+// CHECK: str {{.*}}x30
+// CHECK: //APP
+// CHECK: //NO_APP
+// CHECK: ldr {{.*}}x30
+#[no_mangle]
+pub unsafe fn issue_75761() {
+ asm!("", out("v0") _, out("x30") _);
+}
+
macro_rules! check {
($func:ident $ty:ident $class:ident $mov:literal $modifier:literal) => {
#[no_mangle]
// CHECK: fmov s0, s0
// CHECK: //NO_APP
check_reg!(v0_f64x2 f64x2 "s0" "fmov");
-
-// Regression test for #75761
-pub unsafe fn issue_75761() {
- asm!("", out("v0") _, out("x30") _);
-}
-// compile-flags: -O --target=avr-unknown-unknown --crate-type=rlib
+// compile-flags: -O --target=avr-unknown-gnu-atmega328 --crate-type=rlib
// needs-llvm-components: avr
// This test validates that function pointers can be stored in global variables
fn panic_impl(info: &PanicInfo) -> ! { loop {} }
#[lang = "eh_personality"]
fn eh_personality() {}
+#[lang = "eh_catch_typeinfo"]
+static EH_CATCH_TYPEINFO: u8 = 0;
--- /dev/null
+// compile-flags: -O
+// EMIT_MIR if_condition_int.opt_u32.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.opt_negative.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.opt_char.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.opt_i8.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.dont_opt_bool.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.opt_multiple_ifs.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.dont_remove_comparison.SimplifyComparisonIntegral.diff
+// EMIT_MIR if_condition_int.dont_opt_floats.SimplifyComparisonIntegral.diff
+
+fn opt_u32(x: u32) -> u32 {
+ if x == 42 { 0 } else { 1 }
+}
+
+// don't opt: it is already optimal to switch on the bool
+fn dont_opt_bool(x: bool) -> u32 {
+ if x { 0 } else { 1 }
+}
+
+fn opt_char(x: char) -> u32 {
+ if x == 'x' { 0 } else { 1 }
+}
+
+fn opt_i8(x: i8) -> u32 {
+ if x == 42 { 0 } else { 1 }
+}
+
+fn opt_negative(x: i32) -> u32 {
+ if x == -42 { 0 } else { 1 }
+}
+
+fn opt_multiple_ifs(x: u32) -> u32 {
+ if x == 42 {
+ 0
+ } else if x != 21 {
+ 1
+ } else {
+ 2
+ }
+}
+
+// test that we optimize, but do not remove the b statement, as that is used later on
+fn dont_remove_comparison(a: i8) -> i32 {
+ let b = a == 17;
+ match b {
+ false => 10 + b as i32,
+ true => 100 + b as i32,
+ }
+}
+
+// test that we do not optimize on floats
+fn dont_opt_floats(a: f32) -> i32 {
+ if a == -42.0 { 0 } else { 1 }
+}
+
+fn main() {
+ opt_u32(0);
+ opt_char('0');
+ opt_i8(22);
+ dont_opt_bool(false);
+ opt_negative(0);
+ opt_multiple_ifs(0);
+ dont_remove_comparison(11);
+ dont_opt_floats(1.0);
+}
--- /dev/null
+- // MIR for `dont_opt_bool` before SimplifyComparisonIntegral
++ // MIR for `dont_opt_bool` after SimplifyComparisonIntegral
+
+ fn dont_opt_bool(_1: bool) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:16:18: 16:19
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:16:30: 16:33
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:17:8: 17:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:17:8: 17:9
+ _2 = _1; // scope 0 at $DIR/if-condition-int.rs:17:8: 17:9
+ switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:17:5: 17:26
+ }
+
+ bb1: {
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:17:23: 17:24
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:17:5: 17:26
+ }
+
+ bb2: {
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:17:12: 17:13
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:17:5: 17:26
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:18:1: 18:2
+ return; // scope 0 at $DIR/if-condition-int.rs:18:2: 18:2
+ }
+ }
+
--- /dev/null
+- // MIR for `dont_opt_floats` before SimplifyComparisonIntegral
++ // MIR for `dont_opt_floats` after SimplifyComparisonIntegral
+
+ fn dont_opt_floats(_1: f32) -> i32 {
+ debug a => _1; // in scope 0 at $DIR/if-condition-int.rs:52:20: 52:21
+ let mut _0: i32; // return place in scope 0 at $DIR/if-condition-int.rs:52:31: 52:34
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:53:8: 53:18
+ let mut _3: f32; // in scope 0 at $DIR/if-condition-int.rs:53:8: 53:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:53:8: 53:18
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:53:8: 53:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:53:8: 53:9
+ _2 = Eq(move _3, const -42f32); // scope 0 at $DIR/if-condition-int.rs:53:8: 53:18
+ // mir::Constant
+ // + span: $DIR/if-condition-int.rs:53:13: 53:18
+ // + literal: Const { ty: f32, val: Value(Scalar(0xc2280000)) }
+ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:53:17: 53:18
+ switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:53:5: 53:35
+ }
+
+ bb1: {
+ _0 = const 1_i32; // scope 0 at $DIR/if-condition-int.rs:53:32: 53:33
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:53:5: 53:35
+ }
+
+ bb2: {
+ _0 = const 0_i32; // scope 0 at $DIR/if-condition-int.rs:53:21: 53:22
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:53:5: 53:35
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:54:1: 54:2
+ return; // scope 0 at $DIR/if-condition-int.rs:54:2: 54:2
+ }
+ }
+
--- /dev/null
+- // MIR for `dont_remove_comparison` before SimplifyComparisonIntegral
++ // MIR for `dont_remove_comparison` after SimplifyComparisonIntegral
+
+ fn dont_remove_comparison(_1: i8) -> i32 {
+ debug a => _1; // in scope 0 at $DIR/if-condition-int.rs:43:27: 43:28
+ let mut _0: i32; // return place in scope 0 at $DIR/if-condition-int.rs:43:37: 43:40
+ let _2: bool; // in scope 0 at $DIR/if-condition-int.rs:44:9: 44:10
+ let mut _3: i8; // in scope 0 at $DIR/if-condition-int.rs:44:13: 44:14
+ let mut _4: i32; // in scope 0 at $DIR/if-condition-int.rs:46:23: 46:31
+ let mut _5: bool; // in scope 0 at $DIR/if-condition-int.rs:46:23: 46:24
+ let mut _6: i32; // in scope 0 at $DIR/if-condition-int.rs:47:23: 47:31
+ let mut _7: bool; // in scope 0 at $DIR/if-condition-int.rs:47:23: 47:24
+ scope 1 {
+ debug b => _2; // in scope 1 at $DIR/if-condition-int.rs:44:9: 44:10
+ }
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:44:9: 44:10
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:44:13: 44:14
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:44:13: 44:14
+- _2 = Eq(move _3, const 17_i8); // scope 0 at $DIR/if-condition-int.rs:44:13: 44:20
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:44:19: 44:20
+- switchInt(_2) -> [false: bb2, otherwise: bb1]; // scope 1 at $DIR/if-condition-int.rs:46:9: 46:14
++ _2 = Eq(_3, const 17_i8); // scope 0 at $DIR/if-condition-int.rs:44:13: 44:20
++ nop; // scope 0 at $DIR/if-condition-int.rs:44:19: 44:20
++ switchInt(move _3) -> [17_i8: bb1, otherwise: bb2]; // scope 1 at $DIR/if-condition-int.rs:46:9: 46:14
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 1 at $DIR/if-condition-int.rs:46:9: 46:14
+ StorageLive(_6); // scope 1 at $DIR/if-condition-int.rs:47:23: 47:31
+ StorageLive(_7); // scope 1 at $DIR/if-condition-int.rs:47:23: 47:24
+ _7 = _2; // scope 1 at $DIR/if-condition-int.rs:47:23: 47:24
+ _6 = move _7 as i32 (Misc); // scope 1 at $DIR/if-condition-int.rs:47:23: 47:31
+ StorageDead(_7); // scope 1 at $DIR/if-condition-int.rs:47:30: 47:31
+ _0 = Add(const 100_i32, move _6); // scope 1 at $DIR/if-condition-int.rs:47:17: 47:31
+ StorageDead(_6); // scope 1 at $DIR/if-condition-int.rs:47:30: 47:31
+ goto -> bb3; // scope 1 at $DIR/if-condition-int.rs:45:5: 48:6
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 1 at $DIR/if-condition-int.rs:46:9: 46:14
+ StorageLive(_4); // scope 1 at $DIR/if-condition-int.rs:46:23: 46:31
+ StorageLive(_5); // scope 1 at $DIR/if-condition-int.rs:46:23: 46:24
+ _5 = _2; // scope 1 at $DIR/if-condition-int.rs:46:23: 46:24
+ _4 = move _5 as i32 (Misc); // scope 1 at $DIR/if-condition-int.rs:46:23: 46:31
+ StorageDead(_5); // scope 1 at $DIR/if-condition-int.rs:46:30: 46:31
+ _0 = Add(const 10_i32, move _4); // scope 1 at $DIR/if-condition-int.rs:46:18: 46:31
+ StorageDead(_4); // scope 1 at $DIR/if-condition-int.rs:46:30: 46:31
+ goto -> bb3; // scope 1 at $DIR/if-condition-int.rs:45:5: 48:6
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:49:1: 49:2
+ return; // scope 0 at $DIR/if-condition-int.rs:49:2: 49:2
+ }
+ }
+
--- /dev/null
+- // MIR for `opt_char` before SimplifyComparisonIntegral
++ // MIR for `opt_char` after SimplifyComparisonIntegral
+
+ fn opt_char(_1: char) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:20:13: 20:14
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:20:25: 20:28
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:21:8: 21:16
+ let mut _3: char; // in scope 0 at $DIR/if-condition-int.rs:21:8: 21:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:21:8: 21:16
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:21:8: 21:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:21:8: 21:9
+- _2 = Eq(move _3, const 'x'); // scope 0 at $DIR/if-condition-int.rs:21:8: 21:16
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:21:15: 21:16
+- switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
++ _2 = Eq(_3, const 'x'); // scope 0 at $DIR/if-condition-int.rs:21:8: 21:16
++ nop; // scope 0 at $DIR/if-condition-int.rs:21:15: 21:16
++ switchInt(move _3) -> ['x': bb2, otherwise: bb1]; // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:21:30: 21:31
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:21:19: 21:20
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:21:5: 21:33
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:22:1: 22:2
+ return; // scope 0 at $DIR/if-condition-int.rs:22:2: 22:2
+ }
+ }
+
--- /dev/null
+- // MIR for `opt_i8` before SimplifyComparisonIntegral
++ // MIR for `opt_i8` after SimplifyComparisonIntegral
+
+ fn opt_i8(_1: i8) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:24:11: 24:12
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:24:21: 24:24
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:25:8: 25:15
+ let mut _3: i8; // in scope 0 at $DIR/if-condition-int.rs:25:8: 25:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:25:8: 25:15
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:25:8: 25:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:25:8: 25:9
+- _2 = Eq(move _3, const 42_i8); // scope 0 at $DIR/if-condition-int.rs:25:8: 25:15
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:25:14: 25:15
+- switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
++ _2 = Eq(_3, const 42_i8); // scope 0 at $DIR/if-condition-int.rs:25:8: 25:15
++ nop; // scope 0 at $DIR/if-condition-int.rs:25:14: 25:15
++ switchInt(move _3) -> [42_i8: bb2, otherwise: bb1]; // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:25:29: 25:30
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:25:18: 25:19
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:25:5: 25:32
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:26:1: 26:2
+ return; // scope 0 at $DIR/if-condition-int.rs:26:2: 26:2
+ }
+ }
+
--- /dev/null
+- // MIR for `opt_multiple_ifs` before SimplifyComparisonIntegral
++ // MIR for `opt_multiple_ifs` after SimplifyComparisonIntegral
+
+ fn opt_multiple_ifs(_1: u32) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:32:21: 32:22
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:32:32: 32:35
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:33:8: 33:15
+ let mut _3: u32; // in scope 0 at $DIR/if-condition-int.rs:33:8: 33:9
+ let mut _4: bool; // in scope 0 at $DIR/if-condition-int.rs:35:15: 35:22
+ let mut _5: u32; // in scope 0 at $DIR/if-condition-int.rs:35:15: 35:16
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:33:8: 33:15
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:33:8: 33:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:33:8: 33:9
+- _2 = Eq(move _3, const 42_u32); // scope 0 at $DIR/if-condition-int.rs:33:8: 33:15
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:33:14: 33:15
+- switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
++ _2 = Eq(_3, const 42_u32); // scope 0 at $DIR/if-condition-int.rs:33:8: 33:15
++ nop; // scope 0 at $DIR/if-condition-int.rs:33:14: 33:15
++ switchInt(move _3) -> [42_u32: bb2, otherwise: bb1]; // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
+ StorageLive(_4); // scope 0 at $DIR/if-condition-int.rs:35:15: 35:22
+ StorageLive(_5); // scope 0 at $DIR/if-condition-int.rs:35:15: 35:16
+ _5 = _1; // scope 0 at $DIR/if-condition-int.rs:35:15: 35:16
+- _4 = Ne(move _5, const 21_u32); // scope 0 at $DIR/if-condition-int.rs:35:15: 35:22
+- StorageDead(_5); // scope 0 at $DIR/if-condition-int.rs:35:21: 35:22
+- switchInt(_4) -> [false: bb3, otherwise: bb4]; // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
++ _4 = Ne(_5, const 21_u32); // scope 0 at $DIR/if-condition-int.rs:35:15: 35:22
++ nop; // scope 0 at $DIR/if-condition-int.rs:35:21: 35:22
++ switchInt(move _5) -> [21_u32: bb3, otherwise: bb4]; // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:34:9: 34:10
+ goto -> bb6; // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
+ }
+
+ bb3: {
++ StorageDead(_5); // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
+ _0 = const 2_u32; // scope 0 at $DIR/if-condition-int.rs:38:9: 38:10
+ goto -> bb5; // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
+ }
+
+ bb4: {
++ StorageDead(_5); // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:36:9: 36:10
+ goto -> bb5; // scope 0 at $DIR/if-condition-int.rs:35:12: 39:6
+ }
+
+ bb5: {
+ StorageDead(_4); // scope 0 at $DIR/if-condition-int.rs:39:5: 39:6
+ goto -> bb6; // scope 0 at $DIR/if-condition-int.rs:33:5: 39:6
+ }
+
+ bb6: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:40:1: 40:2
+ return; // scope 0 at $DIR/if-condition-int.rs:40:2: 40:2
+ }
+ }
+
--- /dev/null
+- // MIR for `opt_negative` before SimplifyComparisonIntegral
++ // MIR for `opt_negative` after SimplifyComparisonIntegral
+
+ fn opt_negative(_1: i32) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:28:17: 28:18
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:28:28: 28:31
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:29:8: 29:16
+ let mut _3: i32; // in scope 0 at $DIR/if-condition-int.rs:29:8: 29:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:29:8: 29:16
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:29:8: 29:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:29:8: 29:9
+- _2 = Eq(move _3, const -42_i32); // scope 0 at $DIR/if-condition-int.rs:29:8: 29:16
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:29:15: 29:16
+- switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
++ _2 = Eq(_3, const -42_i32); // scope 0 at $DIR/if-condition-int.rs:29:8: 29:16
++ nop; // scope 0 at $DIR/if-condition-int.rs:29:15: 29:16
++ switchInt(move _3) -> [-42_i32: bb2, otherwise: bb1]; // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:29:30: 29:31
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:29:19: 29:20
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:29:5: 29:33
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:30:1: 30:2
+ return; // scope 0 at $DIR/if-condition-int.rs:30:2: 30:2
+ }
+ }
+
--- /dev/null
+- // MIR for `opt_u32` before SimplifyComparisonIntegral
++ // MIR for `opt_u32` after SimplifyComparisonIntegral
+
+ fn opt_u32(_1: u32) -> u32 {
+ debug x => _1; // in scope 0 at $DIR/if-condition-int.rs:11:12: 11:13
+ let mut _0: u32; // return place in scope 0 at $DIR/if-condition-int.rs:11:23: 11:26
+ let mut _2: bool; // in scope 0 at $DIR/if-condition-int.rs:12:8: 12:15
+ let mut _3: u32; // in scope 0 at $DIR/if-condition-int.rs:12:8: 12:9
+
+ bb0: {
+ StorageLive(_2); // scope 0 at $DIR/if-condition-int.rs:12:8: 12:15
+ StorageLive(_3); // scope 0 at $DIR/if-condition-int.rs:12:8: 12:9
+ _3 = _1; // scope 0 at $DIR/if-condition-int.rs:12:8: 12:9
+- _2 = Eq(move _3, const 42_u32); // scope 0 at $DIR/if-condition-int.rs:12:8: 12:15
+- StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:12:14: 12:15
+- switchInt(_2) -> [false: bb1, otherwise: bb2]; // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
++ _2 = Eq(_3, const 42_u32); // scope 0 at $DIR/if-condition-int.rs:12:8: 12:15
++ nop; // scope 0 at $DIR/if-condition-int.rs:12:14: 12:15
++ switchInt(move _3) -> [42_u32: bb2, otherwise: bb1]; // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
+ }
+
+ bb1: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
+ _0 = const 1_u32; // scope 0 at $DIR/if-condition-int.rs:12:29: 12:30
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
+ }
+
+ bb2: {
++ StorageDead(_3); // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
+ _0 = const 0_u32; // scope 0 at $DIR/if-condition-int.rs:12:18: 12:19
+ goto -> bb3; // scope 0 at $DIR/if-condition-int.rs:12:5: 12:32
+ }
+
+ bb3: {
+ StorageDead(_2); // scope 0 at $DIR/if-condition-int.rs:13:1: 13:2
+ return; // scope 0 at $DIR/if-condition-int.rs:13:2: 13:2
+ }
+ }
+
extern "C" {
void rust_catch_callback(void (*cb)(), bool* rust_ok);
- static void callback() {
+ void throw_cxx_exception() {
println("throwing C++ exception");
throw exception();
}
- void throw_cxx_exception() {
+ void test_cxx_exception() {
bool rust_ok = false;
try {
- rust_catch_callback(callback, &rust_ok);
+ rust_catch_callback(throw_cxx_exception, &rust_ok);
assert(false && "unreachable");
} catch (exception e) {
println("caught C++ exception");
-// Tests that C++ exceptions can unwind through Rust code, run destructors and
-// are ignored by catch_unwind. Also tests that Rust panics can unwind through
+// Tests that C++ exceptions can unwind through Rust code run destructors and
+// are caught by catch_unwind. Also tests that Rust panics can unwind through
// C++ code.
// For linking libstdc++ on MinGW
}
extern "C" {
- fn throw_cxx_exception();
+ fn test_cxx_exception();
#[unwind(allowed)]
fn cxx_catch_callback(cb: extern "C" fn(), ok: *mut bool);
#[no_mangle]
#[unwind(allowed)]
extern "C" fn rust_catch_callback(cb: extern "C" fn(), rust_ok: &mut bool) {
- let _caught_unwind = catch_unwind(AssertUnwindSafe(|| {
- let _drop = DropCheck(rust_ok);
- cb();
- unreachable!("should have unwound instead of returned");
- }));
- unreachable!("catch_unwind should not have caught foreign exception");
+ let _drop = DropCheck(rust_ok);
+ cb();
+ unreachable!("should have unwound instead of returned");
}
-fn throw_rust_panic() {
+fn test_rust_panic() {
#[unwind(allowed)]
extern "C" fn callback() {
println!("throwing rust panic");
}
fn main() {
- unsafe { throw_cxx_exception() };
- throw_rust_panic();
+ unsafe { test_cxx_exception() };
+ test_rust_panic();
}
extern "C" fn __rust_drop_panic() -> ! {
loop {}
}
+
+#[no_mangle]
+extern "C" fn __rust_foreign_exception() -> ! {
+ loop {}
+}
/// [struct@char]
//~^ ERROR incompatible link
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
//~| NOTE resolved to a module
pub mod char {}
pub mod inner {
//! [struct@char]
//~^ ERROR incompatible link
- //~| HELP use its disambiguator
+ //~| HELP prefix with the item kind
//~| NOTE resolved to a builtin type
}
|
LL | #![deny(broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^
-help: to link to the module, prefix with the item type
+help: to link to the module, prefix with the item kind
|
-LL | /// [module@char]
- | ^^^^^^^^^^^
-help: to link to the builtin type, prefix with the item type
+LL | /// [mod@char]
+ | ^^^^^^^^
+help: to link to the builtin type, prefix with the item kind
|
LL | /// [prim@char]
| ^^^^^^^^^
LL | /// [type@char]
| ^^^^^^^^^ ambiguous link
|
-help: to link to the module, prefix with the item type
+help: to link to the module, prefix with the item kind
|
-LL | /// [module@char]
- | ^^^^^^^^^^^
-help: to link to the builtin type, prefix with the item type
+LL | /// [mod@char]
+ | ^^^^^^^^
+help: to link to the builtin type, prefix with the item kind
|
LL | /// [prim@char]
| ^^^^^^^^^
--> $DIR/intra-link-prim-conflict.rs:19:6
|
LL | /// [struct@char]
- | ^^^^^^^^^^^ help: to link to the module, use its disambiguator: `mod@char`
+ | ^^^^^^^^^^^
|
= note: this link resolved to a module, which is not a struct
+help: to link to the module, prefix with the item kind
+ |
+LL | /// [mod@char]
+ | ^^^^^^^^
error: incompatible link kind for `char`
--> $DIR/intra-link-prim-conflict.rs:26:10
|
LL | //! [struct@char]
- | ^^^^^^^^^^^ help: to link to the builtin type, use its disambiguator: `prim@char`
+ | ^^^^^^^^^^^
|
= note: this link resolved to a builtin type, which is not a struct
+help: to link to the builtin type, prefix with the item kind
+ |
+LL | //! [prim@char]
+ | ^^^^^^^^^
error: aborting due to 4 previous errors
|
LL | #![deny(broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^
-help: to link to the struct, prefix with the item type
+help: to link to the struct, prefix with the item kind
|
LL | /// [`struct@ambiguous`] is ambiguous.
| ^^^^^^^^^^^^^^^^^^
LL | /// [ambiguous] is ambiguous.
| ^^^^^^^^^ ambiguous link
|
-help: to link to the struct, prefix with the item type
+help: to link to the struct, prefix with the item kind
|
LL | /// [struct@ambiguous] is ambiguous.
| ^^^^^^^^^^^^^^^^
LL | /// [`multi_conflict`] is a three-way conflict.
| ^^^^^^^^^^^^^^^^ ambiguous link
|
-help: to link to the struct, prefix with the item type
+help: to link to the struct, prefix with the item kind
|
LL | /// [`struct@multi_conflict`] is a three-way conflict.
| ^^^^^^^^^^^^^^^^^^^^^^^
LL | /// Ambiguous [type_and_value].
| ^^^^^^^^^^^^^^ ambiguous link
|
-help: to link to the module, prefix with the item type
+help: to link to the module, prefix with the item kind
|
-LL | /// Ambiguous [module@type_and_value].
- | ^^^^^^^^^^^^^^^^^^^^^
-help: to link to the constant, prefix with the item type
+LL | /// Ambiguous [mod@type_and_value].
+ | ^^^^^^^^^^^^^^^^^^
+help: to link to the constant, prefix with the item kind
|
LL | /// Ambiguous [const@type_and_value].
| ^^^^^^^^^^^^^^^^^^^^
LL | /// Ambiguous non-implied shortcut link [`foo::bar`].
| ^^^^^^^^^^ ambiguous link
|
-help: to link to the enum, prefix with the item type
+help: to link to the enum, prefix with the item kind
|
LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`].
| ^^^^^^^^^^^^^^^
/// Link to [struct@S]
//~^ ERROR incompatible link kind for `S`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [mod@S]
//~^ ERROR incompatible link kind for `S`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [union@S]
//~^ ERROR incompatible link kind for `S`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [trait@S]
//~^ ERROR incompatible link kind for `S`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [struct@T]
//~^ ERROR incompatible link kind for `T`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [derive@m]
//~^ ERROR incompatible link kind for `m`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP add an exclamation mark
/// Link to [const@s]
//~^ ERROR incompatible link kind for `s`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [static@c]
//~^ ERROR incompatible link kind for `c`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [fn@c]
//~^ ERROR incompatible link kind for `c`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [c()]
//~^ ERROR incompatible link kind for `c`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP prefix with the item kind
/// Link to [const@f]
//~^ ERROR incompatible link kind for `f`
//~| NOTE this link resolved
-//~| HELP use its disambiguator
+//~| HELP add parentheses
pub fn f() {}
--> $DIR/intra-links-disambiguator-mismatch.rs:14:14
|
LL | /// Link to [struct@S]
- | ^^^^^^^^ help: to link to the enum, use its disambiguator: `enum@S`
+ | ^^^^^^^^
|
note: the lint level is defined here
--> $DIR/intra-links-disambiguator-mismatch.rs:1:9
LL | #![deny(broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^
= note: this link resolved to an enum, which is not a struct
+help: to link to the enum, prefix with the item kind
+ |
+LL | /// Link to [enum@S]
+ | ^^^^^^
error: incompatible link kind for `S`
--> $DIR/intra-links-disambiguator-mismatch.rs:19:14
|
LL | /// Link to [mod@S]
- | ^^^^^ help: to link to the enum, use its disambiguator: `enum@S`
+ | ^^^^^
|
= note: this link resolved to an enum, which is not a module
+help: to link to the enum, prefix with the item kind
+ |
+LL | /// Link to [enum@S]
+ | ^^^^^^
error: incompatible link kind for `S`
--> $DIR/intra-links-disambiguator-mismatch.rs:24:14
|
LL | /// Link to [union@S]
- | ^^^^^^^ help: to link to the enum, use its disambiguator: `enum@S`
+ | ^^^^^^^
|
= note: this link resolved to an enum, which is not a union
+help: to link to the enum, prefix with the item kind
+ |
+LL | /// Link to [enum@S]
+ | ^^^^^^
error: incompatible link kind for `S`
--> $DIR/intra-links-disambiguator-mismatch.rs:29:14
|
LL | /// Link to [trait@S]
- | ^^^^^^^ help: to link to the enum, use its disambiguator: `enum@S`
+ | ^^^^^^^
|
= note: this link resolved to an enum, which is not a trait
+help: to link to the enum, prefix with the item kind
+ |
+LL | /// Link to [enum@S]
+ | ^^^^^^
error: incompatible link kind for `T`
--> $DIR/intra-links-disambiguator-mismatch.rs:34:14
|
LL | /// Link to [struct@T]
- | ^^^^^^^^ help: to link to the trait, use its disambiguator: `trait@T`
+ | ^^^^^^^^
|
= note: this link resolved to a trait, which is not a struct
+help: to link to the trait, prefix with the item kind
+ |
+LL | /// Link to [trait@T]
+ | ^^^^^^^
error: incompatible link kind for `m`
--> $DIR/intra-links-disambiguator-mismatch.rs:39:14
|
LL | /// Link to [derive@m]
- | ^^^^^^^^ help: to link to the macro, use its disambiguator: `m!`
+ | ^^^^^^^^ help: to link to the macro, add an exclamation mark: `m!`
|
= note: this link resolved to a macro, which is not a derive macro
--> $DIR/intra-links-disambiguator-mismatch.rs:44:14
|
LL | /// Link to [const@s]
- | ^^^^^^^ help: to link to the static, use its disambiguator: `static@s`
+ | ^^^^^^^
|
= note: this link resolved to a static, which is not a constant
+help: to link to the static, prefix with the item kind
+ |
+LL | /// Link to [static@s]
+ | ^^^^^^^^
error: incompatible link kind for `c`
--> $DIR/intra-links-disambiguator-mismatch.rs:49:14
|
LL | /// Link to [static@c]
- | ^^^^^^^^ help: to link to the constant, use its disambiguator: `const@c`
+ | ^^^^^^^^
|
= note: this link resolved to a constant, which is not a static
+help: to link to the constant, prefix with the item kind
+ |
+LL | /// Link to [const@c]
+ | ^^^^^^^
error: incompatible link kind for `c`
--> $DIR/intra-links-disambiguator-mismatch.rs:54:14
|
LL | /// Link to [fn@c]
- | ^^^^ help: to link to the constant, use its disambiguator: `const@c`
+ | ^^^^
|
= note: this link resolved to a constant, which is not a function
+help: to link to the constant, prefix with the item kind
+ |
+LL | /// Link to [const@c]
+ | ^^^^^^^
error: incompatible link kind for `c`
--> $DIR/intra-links-disambiguator-mismatch.rs:59:14
|
LL | /// Link to [c()]
- | ^^^ help: to link to the constant, use its disambiguator: `const@c`
+ | ^^^
|
= note: this link resolved to a constant, which is not a function
+help: to link to the constant, prefix with the item kind
+ |
+LL | /// Link to [const@c]
+ | ^^^^^^^
error: incompatible link kind for `f`
--> $DIR/intra-links-disambiguator-mismatch.rs:64:14
|
LL | /// Link to [const@f]
- | ^^^^^^^ help: to link to the function, use its disambiguator: `f()`
+ | ^^^^^^^ help: to link to the function, add parentheses: `f()`
|
= note: this link resolved to a function, which is not a constant
--- /dev/null
+#![deny(unknown_lints)]
+//~^ NOTE lint level is defined
+#![deny(renamed_and_removed_lints)]
+//~^ NOTE lint level is defined
+#![deny(x)]
+//~^ ERROR unknown lint
+#![deny(intra_doc_link_resolution_failure)]
+//~^ ERROR lint `intra_doc_link_resolution_failure` has been renamed
--- /dev/null
+error: unknown lint: `x`
+ --> $DIR/unknown-renamed-lints.rs:5:9
+ |
+LL | #![deny(x)]
+ | ^
+ |
+note: the lint level is defined here
+ --> $DIR/unknown-renamed-lints.rs:1:9
+ |
+LL | #![deny(unknown_lints)]
+ | ^^^^^^^^^^^^^
+
+error: lint `intra_doc_link_resolution_failure` has been renamed to `broken_intra_doc_links`
+ --> $DIR/unknown-renamed-lints.rs:7:9
+ |
+LL | #![deny(intra_doc_link_resolution_failure)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `broken_intra_doc_links`
+ |
+note: the lint level is defined here
+ --> $DIR/unknown-renamed-lints.rs:3:9
+ |
+LL | #![deny(renamed_and_removed_lints)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: Compilation failed, aborting rustdoc
+
+error: aborting due to 3 previous errors
+
#![crate_name = "foo"]
#![feature(doc_cfg)]
+// @has 'foo/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'
+
// @has 'foo/struct.Foo.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
+// @has '-' '//*[@class="stab portability"]' 'sync'
#[doc(cfg(feature = "sync"))]
#[doc(cfg(feature = "sync"))]
pub struct Foo;
+// @has 'foo/bar/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'
+
// @has 'foo/bar/struct.Bar.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync only.'
#[doc(cfg(feature = "sync"))]
pub mod bar {
#[doc(cfg(feature = "sync"))]
pub struct Bar;
}
+// @has 'foo/baz/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate features `sync` and `send` only'
+
// @has 'foo/baz/struct.Baz.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
#[doc(cfg(all(feature = "sync", feature = "send")))]
pub mod baz {
#[doc(cfg(feature = "sync"))]
}
// @has 'foo/qux/struct.Qux.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
#[doc(cfg(feature = "sync"))]
pub mod qux {
#[doc(cfg(all(feature = "sync", feature = "send")))]
pub struct Qux;
}
+// @has 'foo/quux/index.html'
+// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send and foo and bar$'
+// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` and crate feature `send` and `foo` and `bar` only'
+
// @has 'foo/quux/struct.Quux.html'
-// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" and foo and bar only.'
+// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync and crate feature send and foo and bar only.'
#[doc(cfg(all(feature = "sync", feature = "send", foo)))]
pub mod quux {
#[doc(cfg(all(feature = "send", feature = "sync", bar)))]
struct T;
+struct Tuple(i32);
+
+struct Struct {
+ a: i32
+}
+
+impl Struct {
+ fn method(&self) {}
+}
+
+impl Future for Struct {
+ type Output = Struct;
+ fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> { Poll::Pending }
+}
+
+impl Future for Tuple {
+ type Output = Tuple;
+ fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> { Poll::Pending }
+}
+
impl Future for T {
type Output = Result<(), ()>;
Ok(())
}
+async fn struct_() -> Struct {
+ Struct { a: 1 }
+}
+
+async fn tuple() -> Tuple {
+ Tuple(1i32)
+}
+
async fn baz() -> Result<(), ()> {
let t = T;
t?; //~ ERROR the `?` operator can only be applied to values that implement `std::ops::Try`
+
+ let _: i32 = tuple().0; //~ ERROR no field `0`
+
+ let _: i32 = struct_().a; //~ ERROR no field `a`
+
+ struct_().method(); //~ ERROR no method named
+
Ok(())
}
+async fn match_() {
+ match tuple() {
+ Tuple(_) => {} //~ ERROR mismatched types
+ }
+}
+
fn main() {}
error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
- --> $DIR/issue-61076.rs:22:5
+ --> $DIR/issue-61076.rs:42:5
|
LL | foo()?;
| ^^^^^^
= note: required by `std::ops::Try::into_result`
error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
- --> $DIR/issue-61076.rs:28:5
+ --> $DIR/issue-61076.rs:56:5
|
LL | t?;
| ^^
= help: the trait `std::ops::Try` is not implemented for `T`
= note: required by `std::ops::Try::into_result`
-error: aborting due to 2 previous errors
+error[E0609]: no field `0` on type `impl std::future::Future`
+ --> $DIR/issue-61076.rs:58:26
+ |
+LL | let _: i32 = tuple().0;
+ | ^
+ |
+help: consider awaiting before field access
+ |
+LL | let _: i32 = tuple().await.0;
+ | ^^^^^^
+
+error[E0609]: no field `a` on type `impl std::future::Future`
+ --> $DIR/issue-61076.rs:60:28
+ |
+LL | let _: i32 = struct_().a;
+ | ^
+ |
+help: consider awaiting before field access
+ |
+LL | let _: i32 = struct_().await.a;
+ | ^^^^^^
+
+error[E0599]: no method named `method` found for opaque type `impl std::future::Future` in the current scope
+ --> $DIR/issue-61076.rs:62:15
+ |
+LL | struct_().method();
+ | ^^^^^^ method not found in `impl std::future::Future`
+ |
+help: consider awaiting before this method call
+ |
+LL | struct_().await.method();
+ | ^^^^^^
+
+error[E0308]: mismatched types
+ --> $DIR/issue-61076.rs:69:9
+ |
+LL | async fn tuple() -> Tuple {
+ | ----- the `Output` of this `async fn`'s expected opaque type
+...
+LL | Tuple(_) => {}
+ | ^^^^^^^^ expected opaque type, found struct `Tuple`
+ |
+ = note: expected opaque type `impl std::future::Future`
+ found struct `Tuple`
+help: consider awaiting on the future
+ |
+LL | match tuple().await {
+ | ^^^^^^
+
+error: aborting due to 6 previous errors
-For more information about this error, try `rustc --explain E0277`.
+Some errors have detailed explanations: E0277, E0308, E0599, E0609.
+For more information about an error, try `rustc --explain E0277`.
async fn crash(self) {
Self::partial(self.0);
- Self::full(self); //~ ERROR use of moved value: `self`
+ Self::full(self); //~ ERROR use of partially moved value: `self`
}
}
-error[E0382]: use of moved value: `self`
+error[E0382]: use of partially moved value: `self`
--> $DIR/issue-66958-non-copy-infered-type-arg.rs:11:20
|
LL | Self::partial(self.0);
- | ------ value moved here
+ | ------ value partially moved here
LL | Self::full(self);
| ^^^^ value used here after partial move
|
- = note: move occurs because `self.0` has type `S`, which does not implement the `Copy` trait
+ = note: partial move occurs because `self.0` has type `S`, which does not implement the `Copy` trait
error: aborting due to previous error
LL | match m { _ => { } } // #53114: should eventually be accepted too
| ^ value used here after move
-error[E0382]: use of moved value: `mm`
+error[E0382]: use of partially moved value: `mm`
--> $DIR/issue-53114-borrow-checks.rs:27:11
|
LL | match mm { (_x, _) => { } }
- | -- value moved here
+ | -- value partially moved here
LL | match mm { (_, _y) => { } }
| ^^ value used here after partial move
|
- = note: move occurs because `mm.0` has type `M`, which does not implement the `Copy` trait
+ = note: partial move occurs because `mm.0` has type `M`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `mm`
+error[E0382]: use of partially moved value: `mm`
--> $DIR/issue-53114-borrow-checks.rs:29:11
|
LL | match mm { (_, _y) => { } }
- | -- value moved here
+ | -- value partially moved here
LL |
LL | match mm { (_, _) => { } }
| ^^ value used here after partial move
|
- = note: move occurs because `mm.1` has type `M`, which does not implement the `Copy` trait
+ = note: partial move occurs because `mm.1` has type `M`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `m`
--> $DIR/issue-53114-borrow-checks.rs:36:16
LL | if let _ = m { } // #53114: should eventually be accepted too
| ^ value used here after move
-error[E0382]: use of moved value: `mm`
+error[E0382]: use of partially moved value: `mm`
--> $DIR/issue-53114-borrow-checks.rs:41:22
|
LL | if let (_x, _) = mm { }
- | -- value moved here
+ | -- value partially moved here
LL | if let (_, _y) = mm { }
| ^^ value used here after partial move
|
- = note: move occurs because `mm.0` has type `M`, which does not implement the `Copy` trait
+ = note: partial move occurs because `mm.0` has type `M`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `mm`
+error[E0382]: use of partially moved value: `mm`
--> $DIR/issue-53114-borrow-checks.rs:43:21
|
LL | if let (_, _y) = mm { }
- | -- value moved here
+ | -- value partially moved here
LL |
LL | if let (_, _) = mm { }
| ^^ value used here after partial move
|
- = note: move occurs because `mm.1` has type `M`, which does not implement the `Copy` trait
+ = note: partial move occurs because `mm.1` has type `M`, which does not implement the `Copy` trait
error: aborting due to 6 previous errors
[_, _, (_x, _)] => {}
}
match a {
- [.., _y] => {} //~ ERROR use of moved value
+ [.., _y] => {} //~ ERROR use of partially moved value
}
}
[_x, _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_y @ .., _, _] => {}
}
}
[.., _x] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _, _y @ ..] => {}
}
}
[(_x, _), _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_y @ .., _, _] => {}
}
}
[.., (_x, _)] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _, _y @ ..] => {}
}
}
[x @ .., _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _y @ ..] => {}
}
}
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a[..]`
+error[E0382]: use of partially moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array-match.rs:23:14
|
LL | [_, _, (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
...
LL | [.., _y] => {}
| ^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array-match.rs:33:15
|
= note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-match.rs:44:11
|
LL | [_x, _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-match.rs:55:11
|
LL | [.., _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-match.rs:66:11
|
LL | [(_x, _), _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-match.rs:77:11
|
LL | [.., (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array-match.rs:89:11
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-match.rs:110:11
|
LL | [x @ .., _] => {}
- | ------ value moved here
+ | ------ value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 10 previous errors
[_, _, _x] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., _y, _] => {}
}
}
[_, _, (_x, _)] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., (_, _y)] => {}
}
}
[_x, _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _y @ ..] => {}
}
}
[.., _x] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_y @ .., _] => {}
}
}
[(_x, _), _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _y @ ..] => {}
}
}
[.., (_x, _)] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_y @ .., _] => {}
}
}
[_, _y @ ..] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[(_x, _), _, _] => {}
}
}
[_y @ .., _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., (_x, _)] => {}
}
}
[x @ .., _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, _y @ ..] => {}
}
}
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:17:11
|
LL | [_, _, _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:28:11
|
LL | [_, _, (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:41:11
|
LL | [_x, _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:52:11
|
LL | [.., _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:63:11
|
LL | [(_x, _), _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:74:11
|
LL | [.., (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:85:11
|
LL | [_, _y @ ..] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:96:11
|
LL | [_y @ .., _] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-no-overlap-match.rs:109:11
|
LL | [x @ .., _, _] => {}
- | ------ value moved here
+ | ------ value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 9 previous errors
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a[..]`
+error[E0382]: borrow of partially moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array-use-match.rs:23:14
|
LL | [_, _, (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
...
LL | [.., ref _y] => {}
| ^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: borrow of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array-use-match.rs:33:15
|
= note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:44:11
|
LL | [_x, _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:55:11
|
LL | [.., _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:66:11
|
LL | [(_x, _), _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:77:11
|
LL | [.., (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: borrow of moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array-use-match.rs:89:11
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:110:11
|
LL | [x @ .., _] => {}
- | ------ value moved here
+ | ------ value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:123:5
|
LL | [_, _, _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | a[2] = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:131:5
|
LL | [_, _, (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | a[2].1 = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:139:5
|
LL | [_, _, _x @ ..] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | a[0] = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-match.rs:147:5
|
LL | [_, _, _x @ ..] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | a[0].1 = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 14 previous errors
[_, _, _x] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., ref _y, _] => {}
}
}
[_, _, (_x, _)] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., (_, ref _y)] => {}
}
}
[_x, _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, ref _y @ ..] => {}
}
}
[.., _x] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[ref _y @ .., _] => {}
}
}
[(_x, _), _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, ref _y @ ..] => {}
}
}
[.., (_x, _)] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[ref _y @ .., _] => {}
}
}
[_, _y @ ..] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[(ref _x, _), _, _] => {}
}
}
[_y @ .., _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[.., (ref _x, _)] => {}
}
}
[x @ .., _, _] => {}
}
match a {
- //~^ ERROR use of moved value
+ //~^ ERROR use of partially moved value
[_, ref _y @ ..] => {}
}
}
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:17:11
|
LL | [_, _, _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:28:11
|
LL | [_, _, (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:41:11
|
LL | [_x, _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:52:11
|
LL | [.., _x] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:63:11
|
LL | [(_x, _), _, _] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:74:11
|
LL | [.., (_x, _)] => {}
- | -- value moved here
+ | -- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:85:11
|
LL | [_, _y @ ..] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:96:11
|
LL | [_y @ .., _] => {}
- | ------- value moved here
+ | ------- value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use-no-overlap-match.rs:109:11
|
LL | [x @ .., _, _] => {}
- | ------ value moved here
+ | ------ value partially moved here
LL | }
LL | match a {
| ^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 9 previous errors
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a[..]`
+error[E0382]: borrow of partially moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array-use.rs:16:14
|
LL | let [_, _, (_x, _)] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [.., ref _y] = a;
| ^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: borrow of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array-use.rs:22:15
|
= note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a`
+error[E0382]: borrow of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:30:10
|
LL | let [_x, _, _] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [ref _y @ .., _, _] = a;
| ^^^^^^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a`
+error[E0382]: borrow of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:36:16
|
LL | let [.., _x] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_, _, ref _y @ ..] = a;
| ^^^^^^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a`
+error[E0382]: borrow of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:42:10
|
LL | let [(_x, _), _, _] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [ref _y @ .., _, _] = a;
| ^^^^^^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a`
+error[E0382]: borrow of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:48:16
|
LL | let [.., (_x, _)] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_, _, ref _y @ ..] = a;
| ^^^^^^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: borrow of moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array-use.rs:54:11
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: borrow of moved value: `a`
+error[E0382]: borrow of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:68:13
|
LL | let [x @ .., _] = a;
- | ------ value moved here
+ | ------ value partially moved here
LL | let [_, ref _y @ ..] = a;
| ^^^^^^^^^^^ value borrowed here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:76:5
|
LL | let [_, _, _x] = a;
- | -- value moved here
+ | -- value partially moved here
LL | a[2] = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:82:5
|
LL | let [_, _, (_x, _)] = a;
- | -- value moved here
+ | -- value partially moved here
LL | a[2].1 = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:88:5
|
LL | let [_, _, _x @ ..] = a;
- | ------- value moved here
+ | ------- value partially moved here
LL | a[0] = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array-use.rs:94:5
|
LL | let [_, _, _x @ ..] = a;
- | ------- value moved here
+ | ------- value partially moved here
LL | a[0].1 = Default::default();
| ^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 14 previous errors
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a[..]`
+error[E0382]: use of partially moved value: `a[..]`
--> $DIR/borrowck-move-out-from-array.rs:16:14
|
LL | let [_, _, (_x, _)] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [.., _y] = a;
| ^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array.rs:22:15
|
= note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array.rs:30:10
|
LL | let [_x, _, _] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_y @ .., _, _] = a;
| ^^^^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array.rs:36:16
|
LL | let [.., _x] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_, _, _y @ ..] = a;
| ^^^^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array.rs:42:10
|
LL | let [(_x, _), _, _] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_y @ .., _, _] = a;
| ^^^^^^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array.rs:48:16
|
LL | let [.., (_x, _)] = a;
- | -- value moved here
+ | -- value partially moved here
LL | let [_, _, _y @ ..] = a;
| ^^^^^^^ value used here after partial move
|
- = note: move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..].0` has type `std::string::String`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `a[..].0`
--> $DIR/borrowck-move-out-from-array.rs:54:11
|
= note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `a`
+error[E0382]: use of partially moved value: `a`
--> $DIR/borrowck-move-out-from-array.rs:68:13
|
LL | let [x @ .., _] = a;
- | ------ value moved here
+ | ------ value partially moved here
LL | let [_, _y @ ..] = a;
| ^^^^^^^ value used here after partial move
|
- = note: move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
+ = note: partial move occurs because `a[..]` has type `(std::string::String, std::string::String)`, which does not implement the `Copy` trait
error: aborting due to 10 previous errors
|
= note: move occurs because `line1.origin` has type `Point`, which does not implement the `Copy` trait
-error[E0382]: use of moved value: `line2`
+error[E0382]: use of partially moved value: `line2`
--> $DIR/borrowck-uninit-field-access.rs:29:5
|
LL | let _moved = (line2.origin, line2.middle);
- | ------------ value moved here
+ | ------------ value partially moved here
LL | line2.consume();
| ^^^^^ value used here after partial move
|
- = note: move occurs because `line2.middle` has type `Point`, which does not implement the `Copy` trait
+ = note: partial move occurs because `line2.middle` has type `Point`, which does not implement the `Copy` trait
error: aborting due to 3 previous errors
if let Some(mut x) = s {
x = S;
}
- foo(s); //~ ERROR use of moved value: `s`
+ foo(s); //~ ERROR use of partially moved value: `s`
let mut e = E::V { s: S };
let E::V { s: mut x } = e;
x = S;
- bar(e); //~ ERROR use of moved value: `e`
+ bar(e); //~ ERROR use of partially moved value: `e`
}
-error[E0382]: use of moved value: `s`
+error[E0382]: use of partially moved value: `s`
--> $DIR/move-in-pattern-mut.rs:18:9
|
LL | if let Some(mut x) = s {
- | ----- value moved here
+ | ----- value partially moved here
...
LL | foo(s);
| ^ value used here after partial move
|
- = note: move occurs because value has type `S`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `S`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `s.0`
|
LL | if let Some(ref mut x) = s {
| ^^^
-error[E0382]: use of moved value: `e`
+error[E0382]: use of partially moved value: `e`
--> $DIR/move-in-pattern-mut.rs:22:9
|
LL | let E::V { s: mut x } = e;
- | ----- value moved here
+ | ----- value partially moved here
LL | x = S;
LL | bar(e);
| ^ value used here after partial move
|
- = note: move occurs because value has type `S`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `S`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `e.s`
|
LL | let E::V { s: ref mut x } = e;
if let Some(ref x) = s {
let _ = x;
}
- foo(s); //~ ERROR use of moved value: `s`
+ foo(s); //~ ERROR use of partially moved value: `s`
let e = E::V { s: S };
let E::V { s: ref x } = e;
let _ = x;
- bar(e); //~ ERROR use of moved value: `e`
+ bar(e); //~ ERROR use of partially moved value: `e`
}
if let Some(x) = s {
let _ = x;
}
- foo(s); //~ ERROR use of moved value: `s`
+ foo(s); //~ ERROR use of partially moved value: `s`
let e = E::V { s: S };
let E::V { s: x } = e;
let _ = x;
- bar(e); //~ ERROR use of moved value: `e`
+ bar(e); //~ ERROR use of partially moved value: `e`
}
-error[E0382]: use of moved value: `s`
+error[E0382]: use of partially moved value: `s`
--> $DIR/move-in-pattern.rs:19:9
|
LL | if let Some(x) = s {
- | - value moved here
+ | - value partially moved here
...
LL | foo(s);
| ^ value used here after partial move
|
- = note: move occurs because value has type `S`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `S`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `s.0`
|
LL | if let Some(ref x) = s {
| ^^^
-error[E0382]: use of moved value: `e`
+error[E0382]: use of partially moved value: `e`
--> $DIR/move-in-pattern.rs:23:9
|
LL | let E::V { s: x } = e;
- | - value moved here
+ | - value partially moved here
LL | let _ = x;
LL | bar(e);
| ^ value used here after partial move
|
- = note: move occurs because value has type `S`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `S`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `e.s`
|
LL | let E::V { s: ref x } = e;
#[lang = "eh_personality"]
fn eh() {}
+#[lang = "eh_catch_typeinfo"]
+static EH_CATCH_TYPEINFO: u8 = 0;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
--- /dev/null
+// Test that multi-byte unicode characters with missing parameters do not ICE.
+
+fn main() {
+ println!("\r¡{}")
+ //~^ ERROR 1 positional argument in format string
+}
--- /dev/null
+error: 1 positional argument in format string, but no arguments were given
+ --> $DIR/issue-70381.rs:4:16
+ |
+LL | println!("\r¡{}")
+ | ^^
+
+error: aborting due to previous error
+
y: f32,
z: f32,
}
- extern "C" { fn origin() -> Point3; }
+ extern "C" {
+ fn origin() -> Point3;
+ }
}
mod b {
#[repr(C)]
y: i32,
z: i32, // NOTE: Incorrectly redeclared as i32
}
- extern "C" { fn origin() -> Point3; }
- //~^ WARN `origin` redeclared with a different signature
+ extern "C" {
+ fn origin() -> Point3; //~ WARN `origin` redeclared with a different signature
+ }
}
}
}
}
+// See #75739
+mod non_zero_transparent {
+ mod a1 {
+ use std::num::NonZeroUsize;
+ extern "C" {
+ fn f1() -> NonZeroUsize;
+ }
+ }
+
+ mod b1 {
+ #[repr(transparent)]
+ struct X(NonZeroUsize);
+ use std::num::NonZeroUsize;
+ extern "C" {
+ fn f1() -> X;
+ }
+ }
+
+ mod a2 {
+ use std::num::NonZeroUsize;
+ extern "C" {
+ fn f2() -> NonZeroUsize;
+ }
+ }
+
+ mod b2 {
+ #[repr(transparent)]
+ struct X1(NonZeroUsize);
+
+ #[repr(transparent)]
+ struct X(X1);
+
+ use std::num::NonZeroUsize;
+ extern "C" {
+ // Same case as above, but with two layers of newtyping.
+ fn f2() -> X;
+ }
+ }
+
+ mod a3 {
+ #[repr(transparent)]
+ struct X(core::ptr::NonNull<i32>);
+
+ use std::num::NonZeroUsize;
+ extern "C" {
+ fn f3() -> X;
+ }
+ }
+
+ mod b3 {
+ extern "C" {
+ fn f3() -> core::ptr::NonNull<i32>;
+ }
+ }
+
+ mod a4 {
+ #[repr(transparent)]
+ enum E {
+ X(std::num::NonZeroUsize),
+ }
+ extern "C" {
+ fn f4() -> E;
+ }
+ }
+
+ mod b4 {
+ extern "C" {
+ fn f4() -> std::num::NonZeroUsize;
+ }
+ }
+}
+
mod null_optimised_enums {
mod a {
extern "C" {
}
}
}
+
+#[allow(improper_ctypes)]
+mod unknown_layout {
+ mod a {
+ extern "C" {
+ pub fn generic(l: Link<u32>);
+ }
+ pub struct Link<T> {
+ pub item: T,
+ pub next: *const Link<T>,
+ }
+ }
+
+ mod b {
+ extern "C" {
+ pub fn generic(l: Link<u32>);
+ }
+ pub struct Link<T> {
+ pub item: T,
+ pub next: *const Link<T>,
+ }
+ }
+}
found `unsafe extern "C" fn(sameish_members::b::Point)`
warning: `origin` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:194:22
+ --> $DIR/clashing-extern-fn.rs:197:13
|
-LL | extern "C" { fn origin() -> Point3; }
- | ---------------------- `origin` previously declared here
+LL | fn origin() -> Point3;
+ | ---------------------- `origin` previously declared here
...
-LL | extern "C" { fn origin() -> Point3; }
- | ^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+LL | fn origin() -> Point3;
+ | ^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
|
= note: expected `unsafe extern "C" fn() -> same_sized_members_clash::a::Point3`
found `unsafe extern "C" fn() -> same_sized_members_clash::b::Point3`
warning: `transparent_incorrect` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:217:13
+ --> $DIR/clashing-extern-fn.rs:220:13
|
LL | fn transparent_incorrect() -> T;
| -------------------------------- `transparent_incorrect` previously declared here
found `unsafe extern "C" fn() -> isize`
warning: `missing_return_type` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:235:13
+ --> $DIR/clashing-extern-fn.rs:238:13
|
LL | fn missing_return_type() -> usize;
| ---------------------------------- `missing_return_type` previously declared here
found `unsafe extern "C" fn()`
warning: `non_zero_usize` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:253:13
+ --> $DIR/clashing-extern-fn.rs:256:13
|
LL | fn non_zero_usize() -> core::num::NonZeroUsize;
| ----------------------------------------------- `non_zero_usize` previously declared here
found `unsafe extern "C" fn() -> usize`
warning: `non_null_ptr` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:255:13
+ --> $DIR/clashing-extern-fn.rs:258:13
|
LL | fn non_null_ptr() -> core::ptr::NonNull<usize>;
| ----------------------------------------------- `non_null_ptr` previously declared here
found `unsafe extern "C" fn() -> *const usize`
warning: `option_non_zero_usize_incorrect` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:281:13
+ --> $DIR/clashing-extern-fn.rs:356:13
|
LL | fn option_non_zero_usize_incorrect() -> usize;
| ---------------------------------------------- `option_non_zero_usize_incorrect` previously declared here
found `unsafe extern "C" fn() -> isize`
warning: `option_non_null_ptr_incorrect` redeclared with a different signature
- --> $DIR/clashing-extern-fn.rs:283:13
+ --> $DIR/clashing-extern-fn.rs:358:13
|
LL | fn option_non_null_ptr_incorrect() -> *const usize;
| --------------------------------------------------- `option_non_null_ptr_incorrect` previously declared here
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:20:23
+ --> $DIR/macro-comma-behavior.rs:21:23
|
LL | assert_eq!(1, 1, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:23:23
+ --> $DIR/macro-comma-behavior.rs:24:23
|
LL | assert_ne!(1, 2, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:29:29
+ --> $DIR/macro-comma-behavior.rs:30:29
|
LL | debug_assert_eq!(1, 1, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:32:29
+ --> $DIR/macro-comma-behavior.rs:33:29
|
LL | debug_assert_ne!(1, 2, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:53:19
+ --> $DIR/macro-comma-behavior.rs:54:19
|
LL | format_args!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:71:21
+ --> $DIR/macro-comma-behavior.rs:72:21
|
LL | unimplemented!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:80:24
+ --> $DIR/macro-comma-behavior.rs:81:24
|
LL | write!(f, "{}",)?;
| ^^
#[cfg(std)] use std::fmt;
#[cfg(core)] use core::fmt;
#[cfg(core)] #[lang = "eh_personality"] fn eh_personality() {}
+#[cfg(core)] #[lang = "eh_catch_typeinfo"] static EH_CATCH_TYPEINFO: u8 = 0;
#[cfg(core)] #[lang = "panic_impl"] fn panic_impl(panic: &core::panic::PanicInfo) -> ! { loop {} }
// (see documentation of the similarly-named test in run-pass)
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:20:23
+ --> $DIR/macro-comma-behavior.rs:21:23
|
LL | assert_eq!(1, 1, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:23:23
+ --> $DIR/macro-comma-behavior.rs:24:23
|
LL | assert_ne!(1, 2, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:29:29
+ --> $DIR/macro-comma-behavior.rs:30:29
|
LL | debug_assert_eq!(1, 1, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:32:29
+ --> $DIR/macro-comma-behavior.rs:33:29
|
LL | debug_assert_ne!(1, 2, "{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:37:18
+ --> $DIR/macro-comma-behavior.rs:38:18
|
LL | eprint!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:49:18
+ --> $DIR/macro-comma-behavior.rs:50:18
|
LL | format!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:53:19
+ --> $DIR/macro-comma-behavior.rs:54:19
|
LL | format_args!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:60:17
+ --> $DIR/macro-comma-behavior.rs:61:17
|
LL | print!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:71:21
+ --> $DIR/macro-comma-behavior.rs:72:21
|
LL | unimplemented!("{}",);
| ^^
error: 1 positional argument in format string, but no arguments were given
- --> $DIR/macro-comma-behavior.rs:80:24
+ --> $DIR/macro-comma-behavior.rs:81:24
|
LL | write!(f, "{}",)?;
| ^^
--- /dev/null
+// Regression test for issue #75904
+// Tests that we point at an expression
+// that required the upvar to be moved, rather than just borrowed.
+
+struct NotCopy;
+
+fn main() {
+ let mut a = NotCopy;
+ loop {
+ || { //~ ERROR use of moved value
+ &mut a;
+ a;
+ };
+ }
+}
--- /dev/null
+error[E0382]: use of moved value: `a`
+ --> $DIR/issue-75904-move-closure-loop.rs:10:9
+ |
+LL | let mut a = NotCopy;
+ | ----- move occurs because `a` has type `NotCopy`, which does not implement the `Copy` trait
+LL | loop {
+LL | || {
+ | ^^ value moved into closure here, in previous iteration of loop
+LL | &mut a;
+LL | a;
+ | - use occurs due to use in closure
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0382`.
Some(right) => consume(right),
None => 0
};
- consume(node) + r //~ ERROR use of moved value: `node`
+ consume(node) + r //~ ERROR use of partially moved value: `node`
}
fn consume(v: Box<List>) -> isize {
-error[E0382]: use of moved value: `node`
+error[E0382]: use of partially moved value: `node`
--> $DIR/moves-based-on-type-cyclic-types-issue-4821.rs:13:13
|
LL | Some(right) => consume(right),
- | ----- value moved here
+ | ----- value partially moved here
...
LL | consume(node) + r
| ^^^^ value used here after partial move
|
- = note: move occurs because value has type `std::boxed::Box<List>`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `std::boxed::Box<List>`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `node.next.0`
|
LL | Some(ref right) => consume(right),
Foo {f} => {}
};
- touch(&x); //~ ERROR borrow of moved value: `x`
+ touch(&x); //~ ERROR borrow of partially moved value: `x`
//~^ value borrowed here after partial move
- //~| move occurs because `x.f` has type `std::string::String`
+ //~| partial move occurs because `x.f` has type `std::string::String`
}
fn main() {}
-error[E0382]: borrow of moved value: `x`
+error[E0382]: borrow of partially moved value: `x`
--> $DIR/moves-based-on-type-match-bindings.rs:16:11
|
LL | Foo {f} => {}
- | - value moved here
+ | - value partially moved here
...
LL | touch(&x);
| ^^ value borrowed here after partial move
|
- = note: move occurs because `x.f` has type `std::string::String`, which does not implement the `Copy` trait
+ = note: partial move occurs because `x.f` has type `std::string::String`, which does not implement the `Copy` trait
error: aborting due to previous error
fn main() {
let x = (vec![1, 2, 3], );
drop(x.0);
- drop(x); //~ ERROR use of moved value
+ drop(x); //~ ERROR use of partially moved value
}
-error[E0382]: use of moved value: `x`
+error[E0382]: use of partially moved value: `x`
--> $DIR/move-subpaths-moves-root.rs:4:10
|
LL | drop(x.0);
- | --- value moved here
+ | --- value partially moved here
LL | drop(x);
| ^ value used here after partial move
|
- = note: move occurs because `x.0` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
+ = note: partial move occurs because `x.0` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
error: aborting due to previous error
}
#[lang = "eh_personality"] extern fn eh_personality() {}
+#[lang = "eh_catch_typeinfo"] static EH_CATCH_TYPEINFO: u8 = 0;
#[lang = "panic_impl"] fn panic_impl(panic: &PanicInfo) -> ! { loop {} }
fn panic_impl(info: &PanicInfo) -> ! { loop {} }
#[lang = "eh_personality"]
fn eh_personality() {}
+#[lang = "eh_catch_typeinfo"]
+static EH_CATCH_TYPEINFO: u8 = 0;
#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
#[lang = "eh_personality"]
extern fn eh_personality() {}
+#[cfg(target_os = "emscripten")]
+#[lang = "eh_catch_typeinfo"]
+static EH_CATCH_TYPEINFO: u8 = 0;
// take a reference to any built-in range
error: `#[panic_handler]` function required, but not found
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:24:16
+ --> $DIR/issue-54505-no-std.rs:27:16
|
LL | take_range(0..1);
| ^^^^
found struct `core::ops::Range<{integer}>`
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:29:16
+ --> $DIR/issue-54505-no-std.rs:32:16
|
LL | take_range(1..);
| ^^^
found struct `core::ops::RangeFrom<{integer}>`
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:34:16
+ --> $DIR/issue-54505-no-std.rs:37:16
|
LL | take_range(..);
| ^^
found struct `core::ops::RangeFull`
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:39:16
+ --> $DIR/issue-54505-no-std.rs:42:16
|
LL | take_range(0..=1);
| ^^^^^
found struct `core::ops::RangeInclusive<{integer}>`
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:44:16
+ --> $DIR/issue-54505-no-std.rs:47:16
|
LL | take_range(..5);
| ^^^
found struct `core::ops::RangeTo<{integer}>`
error[E0308]: mismatched types
- --> $DIR/issue-54505-no-std.rs:49:16
+ --> $DIR/issue-54505-no-std.rs:52:16
|
LL | take_range(..=42);
| ^^^^^
// run-pass
// Test inclusive range syntax.
-#![feature(range_is_empty)]
#![allow(unused_braces)]
#![allow(unused_comparisons)]
println!("allocate({:?}) = {:?}", layout, ptr);
}
- ptr.as_non_null_ptr().as_ptr()
+ ptr.as_mut_ptr()
}
unsafe fn deallocate(ptr: *mut u8, layout: Layout) {
}
let memory = if new.size() > old.size() {
- Global.grow(
- NonNull::new_unchecked(ptr),
- old,
- new.size(),
- )
+ Global.grow(NonNull::new_unchecked(ptr), old, new)
} else {
- Global.shrink(NonNull::new_unchecked(ptr), old, new.size())
+ Global.shrink(NonNull::new_unchecked(ptr), old, new)
};
- let ptr = memory.unwrap_or_else(|_| {
- handle_alloc_error(Layout::from_size_align_unchecked(new.size(), old.align()))
- });
+ let ptr = memory.unwrap_or_else(|_| handle_alloc_error(new));
if PRINT {
println!("reallocate({:?}, old={:?}, new={:?}) = {:?}", ptr, old, new, ptr);
}
- ptr.as_non_null_ptr().as_ptr()
+ ptr.as_mut_ptr()
}
fn idx_to_size(i: usize) -> usize {
(Some(y), ()) => {},
_ => {},
}
- x; //~ ERROR use of moved value
+ x; //~ ERROR use of partially moved value
}
LL | x;
| ^ value used here after move
-error[E0382]: use of moved value: `x`
+error[E0382]: use of partially moved value: `x`
--> $DIR/ref-suggestion.rs:16:5
|
LL | (Some(y), ()) => {},
- | - value moved here
+ | - value partially moved here
...
LL | x;
| ^ value used here after partial move
|
- = note: move occurs because value has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
+ = note: partial move occurs because value has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `x.0.0`
|
LL | (Some(ref y), ()) => {},
| -- value moved here
LL | drop_unsized(y);
LL | println!("{}", &x);
- | ^^ value borrowed here after partial move
+ | ^^ value borrowed here after move
|
= note: move occurs because `*x` has type `str`, which does not implement the `Copy` trait
| -- value moved here
LL | y.foo();
LL | println!("{}", &x);
- | ^^ value borrowed here after partial move
+ | ^^ value borrowed here after move
|
= note: move occurs because `*x` has type `str`, which does not implement the `Copy` trait
LL | let _y = *x;
| -- value moved here
LL | drop_unsized(x);
- | ^ value used here after partial move
+ | ^ value used here after move
|
= note: move occurs because `*x` has type `str`, which does not implement the `Copy` trait
LL | let _y = *x;
| -- value moved here
LL | x.foo();
- | ^ value used here after partial move
+ | ^ value used here after move
|
= note: move occurs because `*x` has type `str`, which does not implement the `Copy` trait
[alias]
uitest = "test --test compile-test"
-dev = "run --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --"
+dev = "run --target-dir clippy_dev/target --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --"
[build]
rustflags = ["-Zunstable-options"]
env:
OS: ${{ runner.os }}
+ - name: Test cargo dev new lint
+ run: |
+ cargo dev new_lint --name new_early_pass --pass early
+ cargo dev new_lint --name new_late_pass --pass late
+ cargo check
+ git reset --hard HEAD
+
# Cleanup
- name: Run cargo-cache --autoclean
run: |
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
+[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
+[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
+[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
+[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
+[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
+[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
For general information about `subtree`s in the Rust repository see [Rust's
`CONTRIBUTING.md`][subtree].
+### Patching git-subtree to work with big repos
+
+Currently there's a bug in `git-subtree` that prevents it from working properly
+with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's stale.
+Before continuing with the following steps, we need to manually apply that fix to
+our local copy of `git-subtree`.
+
+You can get the patched version of `git-subtree` from [here][gitgitgadget-pr].
+Put this file under `/usr/lib/git-core` (taking a backup of the previous file)
+and make sure it has the proper permissions:
+
+```bash
+sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree
+sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree
+sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree
+```
+
+_Note:_ The first time running `git subtree push` a cache has to be built. This
+involves going through the complete Clippy history once. For this you have to
+increase the stack limit though, which you can do with `ulimit -s 60000`.
+Make sure to run the `ulimit` command from the same session you call git subtree.
+
+_Note:_ If you are a Debian user, `dash` is the shell used by default for scripts instead of `sh`.
+This shell has a hardcoded recursion limit set to 1000. In order to make this process work,
+you need to force the script to run `bash` instead. You can do this by editing the first
+line of the `git-subtree` script and changing `sh` to `bash`.
+
+### Performing the sync
+
Here is a TL;DR version of the sync process (all of the following commands have
to be run inside the `rust` directory):
# Make sure to change `your-github-name` to your github name in the following command
git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust
```
+
_Note:_ This will directly push to the remote repository. You can also push
to your local copy by replacing the remote address with `/path/to/rust-clippy`
directory.
3. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to
accelerate the process ping the `@rust-lang/clippy` team in your PR and/or
~~annoy~~ ask them in the [Discord] channel.)
-4. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy:
+
+### Syncing back changes in Clippy to [`rust-lang/rust`]
+
+To avoid flooding the [`rust-lang/rust`] PR queue, changes in Clippy's repo are synced back
+in a bi-weekly basis if there's no urgent changes. This is done starting on the day of
+the Rust stable release and then every other week. That way we guarantee that
+every feature in Clippy is available for 2 weeks in nightly, before it can get to beta.
+For reference, the first sync following this cadence was performed the 2020-08-27.
+
+All of the following commands have to be run inside the `rust` directory.
+
+1. Make sure Clippy itself is up-to-date by following the steps outlined in the previous
+section if necessary.
+
+2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy:
```bash
git checkout -b sync-from-clippy
git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master
```
-5. Open a PR to [`rust-lang/rust`]
+3. Open a PR to [`rust-lang/rust`]
+
+### Defining remotes
-Also, you may want to define remotes, so you don't have to type out the remote
+You may want to define remotes, so you don't have to type out the remote
addresses on every sync. You can do this with the following commands (these
commands still have to be run inside the `rust` directory):
$ git subtree push -P src/tools/clippy clippy-local sync-from-rust
```
-_Note:_ The first time running `git subtree push` a cache has to be built. This
-involves going through the complete Clippy history once. For this you have to
-increase the stack limit though, which you can do with `ulimit -s 60000`. For
-this to work, you will need the fix of `git subtree` available
-[here][gitgitgadget-pr].
-
[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree
[`rust-lang/rust`]: https://github.com/rust-lang/rust
# begin automatic update
clippy_lints = { version = "0.0.212", path = "clippy_lints" }
# end automatic update
-semver = "0.9"
+semver = "0.10"
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util"}
tempfile = { version = "3.1.0", optional = true }
lazy_static = "1.0"
[dev-dependencies]
-cargo_metadata = "0.9.1"
+cargo_metadata = "0.11.1"
compiletest_rs = { version = "0.5.0", features = ["tmp"] }
tester = "0.7"
lazy_static = "1.0"
fn create_lint(lint: &LintData) -> io::Result<()> {
let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
"early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
- "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"),
+ "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
_ => {
unreachable!("`pass_type` should only ever be `early` or `late`!");
},
edition = "2018"
[dependencies]
-cargo_metadata = "0.9.1"
+cargo_metadata = "0.11.1"
if_chain = "1.0.0"
itertools = "0.9"
lazy_static = "1.0.2"
smallvec = { version = "1", features = ["union"] }
toml = "0.5.3"
unicode-normalization = "0.1"
-semver = "0.9.0"
+semver = "0.10.0"
# NOTE: cargo requires serde feat in its url dep
# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
url = { version = "2.1.0", features = ["serde"] }
use crate::utils::{
- get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method, SpanlessEq,
+ eq_expr_value, get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method,
};
use crate::utils::{higher, sugg};
use if_chain::if_chain;
return;
}
// lhs op= l op r
- if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, l) {
+ if eq_expr_value(cx, lhs, l) {
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
}
// lhs op= l commutative_op r
- if is_commutative(op.node) && SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, r) {
+ if is_commutative(op.node) && eq_expr_value(cx, lhs, r) {
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
}
}
if visitor.counter == 1 {
// a = a op b
- if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, l) {
+ if eq_expr_value(cx, assignee, l) {
lint(assignee, r);
}
// a = b commutative_op a
// Limited to primitive type as these ops are know to be commutative
- if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, r)
- && cx.typeck_results().expr_ty(assignee).is_primitive_ty()
- {
+ if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
match op.node {
hir::BinOpKind::Add
| hir::BinOpKind::Mul
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- if SpanlessEq::new(self.cx).ignore_fn().eq_expr(self.assignee, expr) {
+ if eq_expr_value(self.cx, self.assignee, expr) {
self.counter += 1;
}
span_lint_and_sugg, span_lint_and_then, without_block_comments,
};
use if_chain::if_chain;
-use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_ast::util::lev_distance::find_best_match_for_name;
+use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_hir::{
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
use crate::utils::{
- get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt, span_lint_and_sugg,
- span_lint_and_then, SpanlessEq,
+ eq_expr_value, get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt,
+ span_lint_and_sugg, span_lint_and_then,
};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
}
}
for (n, expr) in self.terminals.iter().enumerate() {
- if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
+ if eq_expr_value(self.cx, e, expr) {
#[allow(clippy::cast_possible_truncation)]
return Ok(Bool::Term(n as u8));
}
if implements_ord(self.cx, e_lhs);
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
if negate(e_binop.node) == Some(expr_binop.node);
- if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_lhs, expr_lhs);
- if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_rhs, expr_rhs);
+ if eq_expr_value(self.cx, e_lhs, expr_lhs);
+ if eq_expr_value(self.cx, e_rhs, expr_rhs);
then {
#[allow(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
+use crate::utils::{eq_expr_value, SpanlessEq, SpanlessHash};
use crate::utils::{get_parent_expr, higher, if_sequence, snippet, span_lint_and_note, span_lint_and_then};
-use crate::utils::{SpanlessEq, SpanlessHash};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Arm, Block, Expr, ExprKind, MatchSource, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
h.finish()
};
- let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool =
- &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) };
+ let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) };
for (i, j) in search_same(conds, hash, eq) {
span_lint_and_note(
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
// Do not spawn warning if `IFS_SAME_COND` already produced it.
- if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) {
+ if eq_expr_value(cx, lhs, rhs) {
return false;
}
SpanlessEq::new(cx).eq_expr(lhs, rhs)
use crate::utils::{implements_trait, is_entrypoint_fn, is_type_diagnostic_item, return_ty, span_lint};
use if_chain::if_chain;
use itertools::Itertools;
-use rustc_ast::ast::{AttrKind, Attribute};
+use rustc_ast::ast::{Async, AttrKind, Attribute, FnRetTy, ItemKind};
use rustc_ast::token::CommentKind;
use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::emitter::EmitterWriter;
+use rustc_errors::Handler;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
+use rustc_parse::maybe_new_parser_from_source_str;
+use rustc_session::parse::ParseSess;
use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::source_map::{BytePos, MultiSpan, Span};
-use rustc_span::Pos;
+use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
+use rustc_span::{FileName, Pos};
+use std::io;
use std::ops::Range;
use url::Url;
headers
}
-static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate", "async fn main() {"];
-
fn check_code(cx: &LateContext<'_>, text: &str, span: Span) {
- if text.contains("fn main() {") && !LEAVE_MAIN_PATTERNS.iter().any(|p| text.contains(p)) {
+ fn has_needless_main(code: &str) -> bool {
+ let filename = FileName::anon_source_code(code);
+
+ let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
+ let handler = Handler::with_emitter(false, None, box emitter);
+ let sess = ParseSess::with_span_handler(handler, sm);
+
+ let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code.into()) {
+ Ok(p) => p,
+ Err(errs) => {
+ for mut err in errs {
+ err.cancel();
+ }
+ return false;
+ },
+ };
+
+ let mut relevant_main_found = false;
+ loop {
+ match parser.parse_item() {
+ Ok(Some(item)) => match &item.kind {
+ // Tests with one of these items are ignored
+ ItemKind::Static(..)
+ | ItemKind::Const(..)
+ | ItemKind::ExternCrate(..)
+ | ItemKind::ForeignMod(..) => return false,
+ // We found a main function ...
+ ItemKind::Fn(_, sig, _, Some(block)) if item.ident.name == sym!(main) => {
+ let is_async = matches!(sig.header.asyncness, Async::Yes{..});
+ let returns_nothing = match &sig.decl.output {
+ FnRetTy::Default(..) => true,
+ FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
+ _ => false,
+ };
+
+ if returns_nothing && !is_async && !block.stmts.is_empty() {
+ // This main function should be linted, but only if there are no other functions
+ relevant_main_found = true;
+ } else {
+ // This main function should not be linted, we're done
+ return false;
+ }
+ },
+ // Another function was found; this case is ignored too
+ ItemKind::Fn(..) => return false,
+ _ => {},
+ },
+ Ok(None) => break,
+ Err(mut e) => {
+ e.cancel();
+ return false;
+ },
+ }
+ }
+
+ relevant_main_found
+ }
+
+ if has_needless_main(text) {
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
}
}
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
-use crate::utils::{snippet_with_applicability, span_lint_and_sugg, SpanlessEq};
+use crate::utils::{eq_expr_value, snippet_with_applicability, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for double comparisons that could be simplified to a single expression.
},
_ => return,
};
- let mut spanless_eq = SpanlessEq::new(cx).ignore_fn();
- if !(spanless_eq.eq_expr(&llhs, &rlhs) && spanless_eq.eq_expr(&lrhs, &rrhs)) {
+ if !(eq_expr_value(cx, &llhs, &rlhs) && eq_expr_value(cx, &lrhs, &rrhs)) {
return;
}
macro_rules! lint_double_comparison {
cx,
DURATION_SUBSEC,
expr.span,
- &format!("Calling `{}()` is more concise than this calculation", suggested_fn),
+ &format!("calling `{}()` is more concise than this calculation", suggested_fn),
"try",
format!(
"{}.{}()",
cx,
ENUM_CLIKE_UNPORTABLE_VARIANT,
var.span,
- "Clike enum variant discriminant is not portable to 32-bit targets",
+ "C-like enum variant discriminant is not portable to 32-bit targets",
);
};
}
&& name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase())
&& name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric())
{
- span_lint(cx, lint, var.span, "Variant name starts with the enum's name");
+ span_lint(cx, lint, var.span, "variant name starts with the enum's name");
}
if partial_rmatch(item_name, &name) == item_name_chars {
- span_lint(cx, lint, var.span, "Variant name ends with the enum's name");
+ span_lint(cx, lint, var.span, "variant name ends with the enum's name");
}
}
let first = &def.variants[0].ident.name.as_str();
cx,
lint,
span,
- &format!("All variants have the same {}fix: `{}`", what, value),
+ &format!("all variants have the same {}fix: `{}`", what, value),
None,
&format!(
"remove the {}fixes and use full paths to \
use crate::utils::{
- implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then, SpanlessEq,
+ eq_expr_value, implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then,
};
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind};
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
return;
}
- if is_valid_operator(op) && SpanlessEq::new(cx).ignore_fn().eq_expr(left, right) {
+ if is_valid_operator(op) && eq_expr_value(cx, left, right) {
span_lint(
cx,
EQ_OP,
--- /dev/null
+use crate::utils::{match_qpath, paths, span_lint_and_then, sugg};
+use if_chain::if_chain;
+use rustc_ast::util::parser::AssocOp;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for statements of the form `(a - b) < f32::EPSILON` or
+ /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
+ ///
+ /// **Why is this bad?** The code without `.abs()` is more likely to have a bug.
+ ///
+ /// **Known problems:** If the user can ensure that b is larger than a, the `.abs()` is
+ /// technically unneccessary. However, it will make the code more robust and doesn't have any
+ /// large performance implications. If the abs call was deliberately left out for performance
+ /// reasons, it is probably better to state this explicitly in the code, which then can be done
+ /// with an allow.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b) < f32::EPSILON
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b).abs() < f32::EPSILON
+ /// }
+ /// ```
+ pub FLOAT_EQUALITY_WITHOUT_ABS,
+ correctness,
+ "float equality check without `.abs()`"
+}
+
+declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]);
+
+impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let lhs;
+ let rhs;
+
+ // check if expr is a binary expression with a lt or gt operator
+ if let ExprKind::Binary(op, ref left, ref right) = expr.kind {
+ match op.node {
+ BinOpKind::Lt => {
+ lhs = left;
+ rhs = right;
+ },
+ BinOpKind::Gt => {
+ lhs = right;
+ rhs = left;
+ },
+ _ => return,
+ };
+ } else {
+ return;
+ }
+
+ if_chain! {
+
+ // left hand side is a substraction
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub,
+ ..
+ },
+ val_l,
+ val_r,
+ ) = lhs.kind;
+
+ // right hand side matches either f32::EPSILON or f64::EPSILON
+ if let ExprKind::Path(ref epsilon_path) = rhs.kind;
+ if match_qpath(epsilon_path, &paths::F32_EPSILON) || match_qpath(epsilon_path, &paths::F64_EPSILON);
+
+ // values of the substractions on the left hand side are of the type float
+ let t_val_l = cx.typeck_results().expr_ty(val_l);
+ let t_val_r = cx.typeck_results().expr_ty(val_r);
+ if let ty::Float(_) = t_val_l.kind;
+ if let ty::Float(_) = t_val_r.kind;
+
+ then {
+ let sug_l = sugg::Sugg::hir(cx, &val_l, "..");
+ let sug_r = sugg::Sugg::hir(cx, &val_r, "..");
+ // format the suggestion
+ let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
+ // spans the lint
+ span_lint_and_then(
+ cx,
+ FLOAT_EQUALITY_WITHOUT_ABS,
+ expr.span,
+ "float equality check without `.abs()`",
+ | diag | {
+ diag.span_suggestion(
+ lhs.span,
+ "add `.abs()`",
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ );
+ }
+ }
+ }
+}
constant, constant_simple, Constant,
Constant::{Int, F32, F64},
};
-use crate::utils::{get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
+use crate::utils::{eq_expr_value, get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
- if are_exprs_equal(cx, lmul_lhs, lmul_rhs);
- if are_exprs_equal(cx, rmul_lhs, rmul_rhs);
+ if eq_expr_value(cx, lmul_lhs, lmul_rhs);
+ if eq_expr_value(cx, rmul_lhs, rmul_rhs);
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
}
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
- BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && are_exprs_equal(cx, left, test),
- BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && are_exprs_equal(cx, right, test),
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
_ => false,
}
} else {
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
- BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && are_exprs_equal(cx, right, test),
- BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && are_exprs_equal(cx, left, test),
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
_ => false,
}
} else {
}
}
-fn are_exprs_equal(cx: &LateContext<'_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool {
- SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2)
-}
-
/// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match constant_simple(cx, cx.typeck_results(), expr) {
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::UnNeg, expr1_negated) = &expr1.kind {
- if are_exprs_equal(cx, expr1_negated, expr2) {
+ if eq_expr_value(cx, expr1_negated, expr2) {
return Some((false, expr2));
}
}
if let ExprKind::Unary(UnOp::UnNeg, expr2_negated) = &expr2.kind {
- if are_exprs_equal(cx, expr1, expr2_negated) {
+ if eq_expr_value(cx, expr1, expr2_negated) {
return Some((true, expr1));
}
}
args_a.len() == args_b.len() &&
(
["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) ||
- method_name_a.as_str() == "log" && args_a.len() == 2 && are_exprs_equal(cx, &args_a[1], &args_b[1])
+ method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1])
);
}
}
cx,
IF_LET_SOME_RESULT,
expr.span.with_hi(op.span.hi()),
- "Matching on `Some` with `ok()` is redundant",
- &format!("Consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
+ "matching on `Some` with `ok()` is redundant",
+ &format!("consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
sugg,
applicability,
);
cx,
IF_NOT_ELSE,
item.span,
- "Unnecessary boolean `not` operation",
+ "unnecessary boolean `not` operation",
None,
"remove the `!` and swap the blocks of the `if`/`else`",
);
cx,
IF_NOT_ELSE,
item.span,
- "Unnecessary `!=` operation",
+ "unnecessary `!=` operation",
None,
"change to `==` and swap the blocks of the `if`/`else`",
);
cx,
IMPLICIT_SATURATING_SUB,
expr.span,
- "Implicitly performing saturating subtraction",
+ "implicitly performing saturating subtraction",
"try",
- format!("{} = {}.saturating_sub({});", var_name, var_name, 1.to_string()),
+ format!("{} = {}.saturating_sub({});", var_name, var_name, '1'),
Applicability::MachineApplicable,
);
}
cx,
MULTIPLE_INHERENT_IMPL,
*additional_span,
- "Multiple implementations of this structure",
+ "multiple implementations of this structure",
|diag| {
- diag.span_note(*initial_span, "First implementation here");
+ diag.span_note(*initial_span, "first implementation here");
},
)
})
cx,
INT_PLUS_ONE,
block.span,
- "Unnecessary `>= y + 1` or `x - 1 >=`",
+ "unnecessary `>= y + 1` or `x - 1 >=`",
"change it to",
recommendation,
Applicability::MachineApplicable, // snippet
impl EarlyLintPass for IntPlusOne {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
- if let Some(ref rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
- Self::emit_warning(cx, item, rec.clone());
+ if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
+ Self::emit_warning(cx, item, rec);
}
}
}
-use crate::utils::{get_item_name, higher, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty};
+use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty};
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
/// Checks if this type has an `is_empty` method.
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
- /// Special case ranges until `range_is_empty` is stabilized. See issue 3807.
- fn should_skip_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
- higher::range(expr).map_or(false, |_| {
- !cx.tcx
- .features()
- .declared_lib_features
- .iter()
- .any(|(name, _)| name.as_str() == "range_is_empty")
- })
- }
-
/// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
if let ty::AssocKind::Fn = item.kind {
})
}
- if should_skip_range(cx, expr) {
- return false;
- }
-
let ty = &walk_ptrs_ty(cx.typeck_results().expr_ty(expr));
match ty.kind {
ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| {
+++ /dev/null
-use if_chain::if_chain;
-use rustc_errors::Applicability;
-use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
-use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind};
-use rustc_lint::{LateContext, LateLintPass, LintContext};
-use rustc_middle::hir::map::Map;
-use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::subst::GenericArgKind;
-use rustc_session::{declare_lint_pass, declare_tool_lint};
-
-use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_then};
-
-declare_clippy_lint! {
- /// **What it does:** Checks for `let`-bindings, which are subsequently
- /// returned.
- ///
- /// **Why is this bad?** It is just extraneous code. Remove it to make your code
- /// more rusty.
- ///
- /// **Known problems:** None.
- ///
- /// **Example:**
- /// ```rust
- /// fn foo() -> String {
- /// let x = String::new();
- /// x
- /// }
- /// ```
- /// instead, use
- /// ```
- /// fn foo() -> String {
- /// String::new()
- /// }
- /// ```
- pub LET_AND_RETURN,
- style,
- "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
-}
-
-declare_lint_pass!(LetReturn => [LET_AND_RETURN]);
-
-impl<'tcx> LateLintPass<'tcx> for LetReturn {
- fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
- // we need both a let-binding stmt and an expr
- if_chain! {
- if let Some(retexpr) = block.expr;
- if let Some(stmt) = block.stmts.iter().last();
- if let StmtKind::Local(local) = &stmt.kind;
- if local.ty.is_none();
- if local.attrs.is_empty();
- if let Some(initexpr) = &local.init;
- if let PatKind::Binding(.., ident, _) = local.pat.kind;
- if let ExprKind::Path(qpath) = &retexpr.kind;
- if match_qpath(qpath, &[&*ident.name.as_str()]);
- if !last_statement_borrows(cx, initexpr);
- if !in_external_macro(cx.sess(), initexpr.span);
- if !in_external_macro(cx.sess(), retexpr.span);
- if !in_external_macro(cx.sess(), local.span);
- if !in_macro(local.span);
- then {
- span_lint_and_then(
- cx,
- LET_AND_RETURN,
- retexpr.span,
- "returning the result of a `let` binding from a block",
- |err| {
- err.span_label(local.span, "unnecessary `let` binding");
-
- if let Some(snippet) = snippet_opt(cx, initexpr.span) {
- err.multipart_suggestion(
- "return the expression directly",
- vec![
- (local.span, String::new()),
- (retexpr.span, snippet),
- ],
- Applicability::MachineApplicable,
- );
- } else {
- err.span_help(initexpr.span, "this expression can be directly returned");
- }
- },
- );
- }
- }
- }
-}
-
-fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- let mut visitor = BorrowVisitor { cx, borrows: false };
- walk_expr(&mut visitor, expr);
- visitor.borrows
-}
-
-struct BorrowVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- borrows: bool,
-}
-
-impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.borrows {
- return;
- }
-
- if let Some(def_id) = fn_def_id(self.cx, expr) {
- self.borrows = self
- .cx
- .tcx
- .fn_sig(def_id)
- .output()
- .skip_binder()
- .walk()
- .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
- }
-
- walk_expr(self, expr);
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
-}
mod exit;
mod explicit_write;
mod fallible_impl_from;
+mod float_equality_without_abs;
mod float_literal;
mod floating_point_arithmetic;
mod format;
mod large_enum_variant;
mod large_stack_arrays;
mod len_zero;
-mod let_and_return;
mod let_if_seq;
mod let_underscore;
mod lifetimes;
mod regex;
mod repeat_once;
mod returns;
+mod self_assignment;
mod serde_api;
mod shadow;
mod single_component_path_imports;
mod tabs_in_doc_comments;
mod temporary_assignment;
mod to_digit_is_some;
+mod to_string_in_display;
mod trait_bounds;
mod transmute;
mod transmuting_null;
mod unsafe_removed_from_name;
mod unused_io_amount;
mod unused_self;
+mod unused_unit;
mod unwrap;
+mod unwrap_in_result;
mod use_self;
mod useless_conversion;
mod vec;
&exit::EXIT,
&explicit_write::EXPLICIT_WRITE,
&fallible_impl_from::FALLIBLE_IMPL_FROM,
+ &float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
&float_literal::EXCESSIVE_PRECISION,
&float_literal::LOSSY_FLOAT_LITERAL,
&floating_point_arithmetic::IMPRECISE_FLOPS,
&large_stack_arrays::LARGE_STACK_ARRAYS,
&len_zero::LEN_WITHOUT_IS_EMPTY,
&len_zero::LEN_ZERO,
- &let_and_return::LET_AND_RETURN,
&let_if_seq::USELESS_LET_IF_SEQ,
&let_underscore::LET_UNDERSCORE_LOCK,
&let_underscore::LET_UNDERSCORE_MUST_USE,
&methods::SEARCH_IS_SOME,
&methods::SHOULD_IMPLEMENT_TRAIT,
&methods::SINGLE_CHAR_PATTERN,
+ &methods::SINGLE_CHAR_PUSH_STR,
&methods::SKIP_WHILE_NEXT,
&methods::STRING_EXTEND_CHARS,
&methods::SUSPICIOUS_MAP,
&methods::UNINIT_ASSUMED_INIT,
&methods::UNNECESSARY_FILTER_MAP,
&methods::UNNECESSARY_FOLD,
+ &methods::UNNECESSARY_LAZY_EVALUATIONS,
&methods::UNWRAP_USED,
&methods::USELESS_ASREF,
&methods::WRONG_PUB_SELF_CONVENTION,
®ex::INVALID_REGEX,
®ex::TRIVIAL_REGEX,
&repeat_once::REPEAT_ONCE,
+ &returns::LET_AND_RETURN,
&returns::NEEDLESS_RETURN,
- &returns::UNUSED_UNIT,
+ &self_assignment::SELF_ASSIGNMENT,
&serde_api::SERDE_API_MISUSE,
&shadow::SHADOW_REUSE,
&shadow::SHADOW_SAME,
&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
&temporary_assignment::TEMPORARY_ASSIGNMENT,
&to_digit_is_some::TO_DIGIT_IS_SOME,
+ &to_string_in_display::TO_STRING_IN_DISPLAY,
&trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
&trait_bounds::TYPE_REPETITION_IN_BOUNDS,
&transmute::CROSSPOINTER_TRANSMUTE,
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
&unused_io_amount::UNUSED_IO_AMOUNT,
&unused_self::UNUSED_SELF,
+ &unused_unit::UNUSED_UNIT,
&unwrap::PANICKING_UNWRAP,
&unwrap::UNNECESSARY_UNWRAP,
+ &unwrap_in_result::UNWRAP_IN_RESULT,
&use_self::USE_SELF,
&useless_conversion::USELESS_CONVERSION,
&utils::internal_lints::CLIPPY_LINTS_INTERNAL,
store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold));
let too_large_for_stack = conf.too_large_for_stack;
store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack});
+ store.register_late_pass(move || box vec::UselessVec{too_large_for_stack});
store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented);
store.register_late_pass(|| box strings::StringLitAsBytes);
store.register_late_pass(|| box derive::Derive);
store.register_late_pass(|| box types::CharLitAsU8);
- store.register_late_pass(|| box vec::UselessVec);
store.register_late_pass(|| box drop_bounds::DropBounds);
store.register_late_pass(|| box get_last_with_len::GetLastWithLen);
store.register_late_pass(|| box drop_forget_ref::DropForgetRef);
store.register_early_pass(|| box reference::DerefAddrOf);
store.register_early_pass(|| box reference::RefInDeref);
store.register_early_pass(|| box double_parens::DoubleParens);
+ store.register_late_pass(|| box to_string_in_display::ToStringInDisplay::new());
store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval);
store.register_early_pass(|| box if_not_else::IfNotElse);
store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
store.register_early_pass(|| box misc_early::MiscEarlyLints);
store.register_early_pass(|| box redundant_closure_call::RedundantClosureCall);
store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall);
- store.register_early_pass(|| box returns::Return);
- store.register_late_pass(|| box let_and_return::LetReturn);
+ store.register_early_pass(|| box unused_unit::UnusedUnit);
+ store.register_late_pass(|| box returns::Return);
store.register_early_pass(|| box collapsible_if::CollapsibleIf);
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
store.register_early_pass(|| box precedence::Precedence);
store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive);
store.register_late_pass(|| box repeat_once::RepeatOnce);
+ store.register_late_pass(|| box unwrap_in_result::UnwrapInResult);
+ store.register_late_pass(|| box self_assignment::SelfAssignment);
+ store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
LintId::of(&shadow::SHADOW_REUSE),
LintId::of(&shadow::SHADOW_SAME),
LintId::of(&strings::STRING_ADD),
+ LintId::of(&unwrap_in_result::UNWRAP_IN_RESULT),
LintId::of(&verbose_file_reads::VERBOSE_FILE_READS),
LintId::of(&write::PRINT_STDOUT),
LintId::of(&write::USE_DEBUG),
LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION),
LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(&explicit_write::EXPLICIT_WRITE),
+ LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(&float_literal::EXCESSIVE_PRECISION),
LintId::of(&format::USELESS_FORMAT),
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
LintId::of(&len_zero::LEN_ZERO),
- LintId::of(&let_and_return::LET_AND_RETURN),
LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
LintId::of(&methods::SEARCH_IS_SOME),
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
LintId::of(&methods::SINGLE_CHAR_PATTERN),
+ LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
LintId::of(&methods::SKIP_WHILE_NEXT),
LintId::of(&methods::STRING_EXTEND_CHARS),
LintId::of(&methods::SUSPICIOUS_MAP),
LintId::of(&methods::UNINIT_ASSUMED_INIT),
LintId::of(&methods::UNNECESSARY_FILTER_MAP),
LintId::of(&methods::UNNECESSARY_FOLD),
+ LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS),
LintId::of(&methods::USELESS_ASREF),
LintId::of(&methods::WRONG_SELF_CONVENTION),
LintId::of(&methods::ZST_OFFSET),
LintId::of(®ex::INVALID_REGEX),
LintId::of(®ex::TRIVIAL_REGEX),
LintId::of(&repeat_once::REPEAT_ONCE),
+ LintId::of(&returns::LET_AND_RETURN),
LintId::of(&returns::NEEDLESS_RETURN),
- LintId::of(&returns::UNUSED_UNIT),
+ LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),
LintId::of(&transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(&unused_unit::UNUSED_UNIT),
LintId::of(&unwrap::PANICKING_UNWRAP),
LintId::of(&unwrap::UNNECESSARY_UNWRAP),
LintId::of(&useless_conversion::USELESS_CONVERSION),
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
LintId::of(&len_zero::LEN_ZERO),
- LintId::of(&let_and_return::LET_AND_RETURN),
LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
LintId::of(&loops::EMPTY_LOOP),
LintId::of(&loops::FOR_KV_MAP),
LintId::of(&methods::OPTION_MAP_OR_NONE),
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
LintId::of(&methods::STRING_EXTEND_CHARS),
LintId::of(&methods::UNNECESSARY_FOLD),
+ LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS),
LintId::of(&methods::WRONG_SELF_CONVENTION),
LintId::of(&misc::TOPLEVEL_REF_ARG),
LintId::of(&misc::ZERO_PTR),
LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
LintId::of(®ex::TRIVIAL_REGEX),
+ LintId::of(&returns::LET_AND_RETURN),
LintId::of(&returns::NEEDLESS_RETURN),
- LintId::of(&returns::UNUSED_UNIT),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&strings::STRING_LIT_AS_BYTES),
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(&types::FN_TO_NUMERIC_CAST),
LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(&unused_unit::UNUSED_UNIT),
LintId::of(&write::PRINTLN_EMPTY_STRING),
LintId::of(&write::PRINT_LITERAL),
LintId::of(&write::PRINT_WITH_NEWLINE),
LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(&eq_op::EQ_OP),
LintId::of(&erasing_op::ERASING_OP),
+ LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
LintId::of(&if_let_mutex::IF_LET_MUTEX),
LintId::of(&ptr::MUT_FROM_REF),
LintId::of(&ranges::REVERSED_EMPTY_RANGES),
LintId::of(®ex::INVALID_REGEX),
+ LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(&swap::ALMOST_SWAPPED),
+ LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(&transmute::WRONG_TRANSMUTE),
LintId::of(&transmuting_null::TRANSMUTING_NULL),
if same_item_push_visitor.should_lint {
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
// Make sure that the push does not involve possibly mutating values
- if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
- if let PatKind::Wild = pat.kind {
- let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
- let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
-
+ if let PatKind::Wild = pat.kind {
+ let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
+ let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
+ if let ExprKind::Path(ref qpath) = pushed_item.kind {
+ if_chain! {
+ if let Res::Local(hir_id) = qpath_res(cx, qpath, pushed_item.hir_id);
+ let node = cx.tcx.hir().get(hir_id);
+ if let Node::Binding(pat) = node;
+ if let PatKind::Binding(bind_ann, ..) = pat.kind;
+ if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
+ then {
+ span_lint_and_help(
+ cx,
+ SAME_ITEM_PUSH,
+ vec.span,
+ "it looks like the same item is being pushed into this Vec",
+ None,
+ &format!(
+ "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
+ item_str, vec_str, item_str
+ ),
+ )
+ }
+ }
+ } else if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
span_lint_and_help(
cx,
SAME_ITEM_PUSH,
cx,
MAP_CLONE,
root.trim_start(receiver).unwrap(),
- "You are needlessly cloning iterator elements",
- "Remove the `map` call",
+ "you are needlessly cloning iterator elements",
+ "remove the `map` call",
String::new(),
Applicability::MachineApplicable,
)
cx,
MAP_CLONE,
replace,
- "You are using an explicit closure for copying elements",
- "Consider calling the dedicated `copied` method",
+ "you are using an explicit closure for copying elements",
+ "consider calling the dedicated `copied` method",
format!(
"{}.copied()",
snippet_with_applicability(cx, root, "..", &mut applicability)
cx,
MAP_CLONE,
replace,
- "You are using an explicit closure for cloning elements",
- "Consider calling the dedicated `cloned` method",
+ "you are using an explicit closure for cloning elements",
+ "consider calling the dedicated `cloned` method",
format!(
"{}.cloned()",
snippet_with_applicability(cx, root, "..", &mut applicability)
-use crate::utils::{is_type_diagnostic_item, is_type_lang_item, snippet, span_lint_and_sugg};
use crate::utils::walk_ptrs_ty;
+use crate::utils::{is_type_diagnostic_item, is_type_lang_item, snippet, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
mod manual_saturating_arithmetic;
mod option_map_unwrap_or;
mod unnecessary_filter_map;
+mod unnecessary_lazy_eval;
use std::borrow::Cow;
use std::fmt;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::subst::GenericArgKind;
-use rustc_middle::ty::{self, Ty, TyS};
+use rustc_middle::ty::{self, TraitRef, Ty, TyS};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, SymbolStr};
use crate::consts::{constant, Constant};
use crate::utils::usage::mutated_variables;
use crate::utils::{
- get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy,
- is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
- match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths,
- remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite,
- span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty,
- walk_ptrs_ty_depth, SpanlessEq,
+ contains_ty, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro,
+ is_copy, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats,
+ last_path_segment, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls,
+ method_chain_args, paths, remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability,
+ snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
+ span_lint_and_then, sugg, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
};
declare_clippy_lint! {
/// **Known problems:** None.
///
/// **Example:**
+ /// In an impl block:
/// ```rust
/// # struct Foo;
/// # struct NotAFoo;
///
/// ```rust
/// # struct Foo;
- /// # struct FooError;
+ /// struct Bar(Foo);
/// impl Foo {
- /// // Good. Return type contains `Self`
- /// fn new() -> Result<Foo, FooError> {
- /// # Ok(Foo)
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
/// }
/// }
/// ```
///
/// ```rust
/// # struct Foo;
- /// struct Bar(Foo);
+ /// # struct FooError;
/// impl Foo {
- /// // Bad. The type name must contain `Self`.
- /// fn new() -> Bar {
- /// # Bar(Foo)
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
/// }
/// }
/// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
pub NEW_RET_NO_SELF,
style,
"not returning type containing `Self` in a `new` method"
/// call_some_ffi_func(c_str);
/// }
/// ```
- /// Here `c_str` point to a freed address. The correct use would be:
+ /// Here `c_str` points to a freed address. The correct use would be:
/// ```rust
/// # use std::ffi::CString;
/// # fn call_some_ffi_func(_: *const i8) {}
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
}
+declare_clippy_lint! {
+ /// **What it does:** Warns when using push_str with a single-character string literal,
+ /// and push with a char would work fine.
+ ///
+ /// **Why is this bad?** It's less clear that we are pushing a single character
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```
+ /// let mut string = String::new();
+ /// string.push_str("R");
+ /// ```
+ /// Could be written as
+ /// ```
+ /// let mut string = String::new();
+ /// string.push('R');
+ /// ```
+ pub SINGLE_CHAR_PUSH_STR,
+ style,
+ "`push_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// **Known problems:** It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
declare_lint_pass!(Methods => [
UNWRAP_USED,
EXPECT_USED,
INEFFICIENT_TO_STRING,
NEW_RET_NO_SELF,
SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_PUSH_STR,
SEARCH_IS_SOME,
TEMPORARY_CSTRING_AS_PTR,
FILTER_NEXT,
ZST_OFFSET,
FILETYPE_IS_FILE,
OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
]);
impl<'tcx> LateLintPass<'tcx> for Methods {
["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]),
["expect", ..] => lint_expect(cx, expr, arg_lists[0]),
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]),
- ["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
+ ["unwrap_or_else", "map"] => {
+ if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]) {
+ unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or");
+ }
+ },
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
["and_then", ..] => {
+ unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "and");
bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]);
bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]);
},
["or_else", ..] => {
+ unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "or");
bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]);
},
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
+ ["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or"),
+ ["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "get_or_insert"),
+ ["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "ok_or"),
_ => {},
}
inefficient_to_string::lint(cx, expr, &args[0], self_ty);
}
+ if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ if match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
+ lint_single_char_push_string(cx, expr, args);
+ }
+ }
+
match self_ty.kind {
ty::Ref(_, ty, _) if ty.kind == ty::Str => {
for &(method, pos) in &PATTERN_METHODS {
}
}
+ #[allow(clippy::too_many_lines)]
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
if in_external_macro(cx.sess(), impl_item.span) {
return;
then {
if cx.access_levels.is_exported(impl_item.hir_id) {
- // check missing trait implementations
- for &(method_name, n_args, fn_header, self_kind, out_type, trait_name) in &TRAIT_METHODS {
- if name == method_name &&
- sig.decl.inputs.len() == n_args &&
- out_type.matches(cx, &sig.decl.output) &&
- self_kind.matches(cx, self_ty, first_arg_ty) &&
- fn_header_equals(*fn_header, sig.header) {
- span_lint(cx, SHOULD_IMPLEMENT_TRAIT, impl_item.span, &format!(
- "defining a method called `{}` on this type; consider implementing \
- the `{}` trait or choosing a less ambiguous name", name, trait_name));
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name &&
+ sig.decl.inputs.len() == method_config.param_count &&
+ method_config.output_type.matches(cx, &sig.decl.output) &&
+ method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
+ fn_header_equals(method_config.fn_header, sig.header) &&
+ method_config.lifetime_param_cond(&impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name,
+ method_config.trait_name,
+ method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ )
+ );
}
}
}
}
}
+ // if this impl block implements a trait, lint in trait definition instead
+ if let hir::ItemKind::Impl { of_trait: Some(_), .. } = item.kind {
+ return;
+ }
+
if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
let ret_ty = return_ty(cx, impl_item.hir_id);
- let contains_self_ty = |ty: Ty<'tcx>| {
- ty.walk().any(|inner| match inner.unpack() {
- GenericArgKind::Type(inner_ty) => TyS::same_type(self_ty, inner_ty),
-
- GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
- })
- };
-
// walk the return type and check for Self (this does not check associated types)
- if contains_self_ty(ret_ty) {
+ if contains_ty(ret_ty, self_ty) {
return;
}
for &(predicate, _span) in cx.tcx.predicates_of(def_id).predicates {
if let ty::PredicateAtom::Projection(projection_predicate) = predicate.skip_binders() {
// walk the associated type and check for Self
- if contains_self_ty(projection_predicate.ty) {
+ if contains_ty(projection_predicate.ty, self_ty) {
return;
}
}
}
}
}
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, item.span);
+ if item.ident.name == sym!(new);
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id);
+ let self_ty = TraitRef::identity(cx.tcx, item.hir_id.owner.to_def_id()).self_ty();
+ if !contains_ty(ret_ty, self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
}
/// Checks for the `OR_FUN_CALL` lint.
return;
};
+ let snippet = snippet_with_macro_callsite(cx, arg.span, "_");
+
span_lint_and_sugg(
cx,
CLONE_ON_REF_PTR,
expr.span,
"using `.clone()` on a ref-counted pointer",
"try this",
- format!(
- "{}::<{}>::clone(&{})",
- caller_type,
- subst.type_at(0),
- snippet(cx, arg.span, "_")
- ),
+ format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet),
Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak
);
}
cx,
ITER_NEXT_SLICE,
expr.span,
- "Using `.iter().next()` on a Slice without end index.",
+ "using `.iter().next()` on a Slice without end index",
"try calling",
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx),
applicability,
cx,
ITER_NEXT_SLICE,
expr.span,
- "Using `.iter().next()` on an array",
+ "using `.iter().next()` on an array",
"try calling",
format!(
"{}.get(0)",
}
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
+/// Return true if lint triggered
fn lint_map_unwrap_or_else<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
map_args: &'tcx [hir::Expr<'_>],
unwrap_args: &'tcx [hir::Expr<'_>],
-) {
+) -> bool {
// lint if the caller of `map()` is an `Option`
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(option_type));
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(result_type));
let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx);
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
- return;
+ return false;
}
} else {
- return;
+ return false;
}
// lint message
map_snippet, unwrap_snippet,
),
);
+ return true;
} else if same_span && multiline {
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
- };
+ return true;
+ }
}
+
+ false
}
/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
}
}
-/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
-fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
+fn get_hint_if_single_char_arg(
+ cx: &LateContext<'_>,
+ arg: &hir::Expr<'_>,
+ applicability: &mut Applicability,
+) -> Option<String> {
if_chain! {
if let hir::ExprKind::Lit(lit) = &arg.kind;
if let ast::LitKind::Str(r, style) = lit.node;
- if r.as_str().len() == 1;
+ let string = r.as_str();
+ if string.len() == 1;
then {
- let mut applicability = Applicability::MachineApplicable;
- let snip = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
+ let snip = snippet_with_applicability(cx, arg.span, &string, applicability);
let ch = if let ast::StrStyle::Raw(nhash) = style {
let nhash = nhash as usize;
// for raw string: r##"a"##
&snip[1..(snip.len() - 1)]
};
let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch });
- span_lint_and_sugg(
- cx,
- SINGLE_CHAR_PATTERN,
- arg.span,
- "single-character string constant used as pattern",
- "try using a `char` instead",
- hint,
- applicability,
- );
+ Some(hint)
+ } else {
+ None
}
}
}
+/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
+fn lint_single_char_pattern(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability) {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PATTERN,
+ arg.span,
+ "single-character string constant used as pattern",
+ "try using a `char` instead",
+ hint,
+ applicability,
+ );
+ }
+}
+
+/// lint for length-1 `str`s as argument for `push_str`
+fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) {
+ let base_string_snippet = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
+ let sugg = format!("{}.push({})", base_string_snippet, extension_string);
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PUSH_STR,
+ expr.span,
+ "calling `push_str()` using a single-character string literal",
+ "consider using `push` with a character literal",
+ sugg,
+ applicability,
+ );
+ }
+}
+
/// Checks for the `USELESS_ASREF` lint.
fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) {
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
];
let is_deref = match map_args[1].kind {
- hir::ExprKind::Path(ref expr_qpath) => deref_aliases.iter().any(|path| match_qpath(expr_qpath, path)),
+ hir::ExprKind::Path(ref expr_qpath) => cx
+ .qpath_res(expr_qpath, map_args[1].hir_id)
+ .opt_def_id()
+ .map_or(false, |fun_def_id| {
+ deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
+ }),
hir::ExprKind::Closure(_, _, body_id, _, _) => {
let closure_body = cx.tcx.hir().body(body_id);
let closure_expr = remove_blocks(&closure_body.value);
abi: rustc_target::spec::abi::Abi::Rust,
};
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
#[rustfmt::skip]
-const TRAIT_METHODS: [(&str, usize, &hir::FnHeader, SelfKind, OutType, &str); 30] = [
- ("add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Add"),
- ("as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::convert::AsMut"),
- ("as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::convert::AsRef"),
- ("bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitAnd"),
- ("bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitOr"),
- ("bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitXor"),
- ("borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::borrow::Borrow"),
- ("borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::borrow::BorrowMut"),
- ("clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::clone::Clone"),
- ("cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::cmp::Ord"),
- ("default", 0, &FN_HEADER, SelfKind::No, OutType::Any, "std::default::Default"),
- ("deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Deref"),
- ("deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::DerefMut"),
- ("div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Div"),
- ("drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, "std::ops::Drop"),
- ("eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, "std::cmp::PartialEq"),
- ("from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::iter::FromIterator"),
- ("from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::str::FromStr"),
- ("hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, "std::hash::Hash"),
- ("index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Index"),
- ("index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::IndexMut"),
- ("into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::iter::IntoIterator"),
- ("mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Mul"),
- ("neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Neg"),
- ("next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, "std::iter::Iterator"),
- ("not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Not"),
- ("rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Rem"),
- ("shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shl"),
- ("shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shr"),
- ("sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Sub"),
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ // FIXME: default doesn't work
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
];
#[rustfmt::skip]
--- /dev/null
+use crate::utils::{is_type_diagnostic_item, match_qpath, snippet, span_lint_and_sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::UNNECESSARY_LAZY_EVALUATIONS;
+
+// Return true if the expression is an accessor of any of the arguments
+fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool {
+ params.iter().any(|arg| {
+ if_chain! {
+ if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind;
+ if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind;
+ if let [p, ..] = path.segments;
+ then {
+ ident.name == p.ident.name
+ } else {
+ false
+ }
+ }
+ })
+}
+
+fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool {
+ paths.iter().any(|candidate| match_qpath(path, candidate))
+}
+
+fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool {
+ match expr.kind {
+ // Closures returning literals can be unconditionally simplified
+ hir::ExprKind::Lit(_) => true,
+
+ hir::ExprKind::Index(ref object, ref index) => {
+ // arguments are not being indexed into
+ if expr_uses_argument(object, params) {
+ false
+ } else {
+ // arguments are not used as index
+ !expr_uses_argument(index, params)
+ }
+ },
+
+ // Reading fields can be simplified if the object is not an argument of the closure
+ hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params),
+
+ // Paths can be simplified if the root is not the argument, this also covers None
+ hir::ExprKind::Path(_) => !expr_uses_argument(expr, params),
+
+ // Calls to Some, Ok, Err can be considered literals if they don't derive an argument
+ hir::ExprKind::Call(ref func, ref args) => if_chain! {
+ if variant_calls; // Disable lint when rules conflict with bind_instead_of_map
+ if let hir::ExprKind::Path(ref path) = func.kind;
+ if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]);
+ then {
+ // Recursively check all arguments
+ args.iter().all(|arg| can_simplify(arg, params, variant_calls))
+ } else {
+ false
+ }
+ },
+
+ // For anything more complex than the above, a closure is probably the right solution,
+ // or the case is handled by an other lint
+ _ => false,
+ }
+}
+
+/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
+/// replaced with `<fn>(return value of simple closure)`
+pub(super) fn lint<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ args: &'tcx [hir::Expr<'_>],
+ allow_variant_calls: bool,
+ simplify_using: &str,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type));
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(result_type));
+
+ if is_option || is_result {
+ if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind {
+ let body = cx.tcx.hir().body(eid);
+ let ex = &body.value;
+ let params = &body.params;
+
+ if can_simplify(ex, params, allow_variant_calls) {
+ let msg = if is_option {
+ "unnecessary closure used to substitute value for `Option::None`"
+ } else {
+ "unnecessary closure used to substitute value for `Result::Err`"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ expr.span,
+ msg,
+ &format!("Use `{}` instead", simplify_using),
+ format!(
+ "{0}.{1}({2})",
+ snippet(cx, args[0].span, ".."),
+ simplify_using,
+ snippet(cx, ex.span, ".."),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
return;
}
let binding = match expr.kind {
- ExprKind::Path(hir::QPath::LangItem(..)) => None,
- ExprKind::Path(ref qpath) => {
+ ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
let binding = last_path_segment(qpath).ident.as_str();
if binding.starts_with('_') &&
!binding.starts_with("__") &&
/// **What it does:** Detects passing a mutable reference to a function that only
/// requires an immutable reference.
///
- /// **Why is this bad?** The immutable reference rules out all other references
- /// to the value. Also the code misleads about the intent of the call site.
+ /// **Why is this bad?** The mutable reference rules out all other references to
+ /// the value. Also the code misleads about the intent of the call site.
///
/// **Known problems:** None.
///
arguments,
cx.typeck_results().expr_ty(fn_expr),
&rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)),
+ "function",
);
}
},
let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
let substs = cx.typeck_results().node_substs(e.hir_id);
let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs);
- check_arguments(cx, arguments, method_type, &path.ident.as_str())
+ check_arguments(cx, arguments, method_type, &path.ident.as_str(), "method")
},
_ => (),
}
}
}
-fn check_arguments<'tcx>(cx: &LateContext<'tcx>, arguments: &[Expr<'_>], type_definition: Ty<'tcx>, name: &str) {
+fn check_arguments<'tcx>(
+ cx: &LateContext<'tcx>,
+ arguments: &[Expr<'_>],
+ type_definition: Ty<'tcx>,
+ name: &str,
+ fn_kind: &str,
+) {
match type_definition.kind {
ty::FnDef(..) | ty::FnPtr(_) => {
let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
cx,
UNNECESSARY_MUT_PASSED,
argument.span,
- &format!("The function/method `{}` doesn't need a mutable reference", name),
+ &format!("the {} `{}` doesn't need a mutable reference", fn_kind, name),
);
}
},
let mutex_param = subst.type_at(0);
if let Some(atomic_name) = get_atomic_name(mutex_param) {
let msg = format!(
- "Consider using an `{}` instead of a `Mutex` here. If you just want the locking \
- behavior and not the internal type, consider using `Mutex<()>`.",
+ "consider using an `{}` instead of a `Mutex` here; if you just want the locking \
+ behavior and not the internal type, consider using `Mutex<()>`",
atomic_name
);
match mutex_param.kind {
needs_check_adjustment = false;
},
ExprKind::Field(..) => {
- dereferenced_expr = parent_expr;
needs_check_adjustment = true;
+
+ // Check whether implicit dereferences happened;
+ // if so, no need to go further up
+ // because of the same reason as the `ExprKind::Unary` case.
+ if cx
+ .typeck_results()
+ .expr_adjustments(dereferenced_expr)
+ .iter()
+ .any(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ {
+ break;
+ }
+
+ dereferenced_expr = parent_expr;
},
ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => {
// `e[i]` => desugared to `*Index::index(&e, i)`,
use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
+use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
}
}
- if let ExprKind::Unary(UnOp::Neg, ref rhs) = expr.kind {
- if let ExprKind::MethodCall(ref path_segment, ref args, _) = rhs.kind {
+ if let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind {
+ let mut arg = operand;
+
+ let mut all_odd = true;
+ while let ExprKind::MethodCall(path_segment, args, _) = &arg.kind {
let path_segment_str = path_segment.ident.name.as_str();
- if let Some(slf) = args.first() {
- if let ExprKind::Lit(ref lit) = slf.kind {
- match lit.kind {
- LitKind::Int(..) | LitKind::Float(..) => {
- if ALLOWED_ODD_FUNCTIONS
- .iter()
- .any(|odd_function| **odd_function == *path_segment_str)
- {
- return;
- }
- let mut applicability = Applicability::MachineApplicable;
- span_lint_and_sugg(
- cx,
- PRECEDENCE,
- expr.span,
- "unary minus has lower precedence than method call",
- "consider adding parentheses to clarify your intent",
- format!(
- "-({})",
- snippet_with_applicability(cx, rhs.span, "..", &mut applicability)
- ),
- applicability,
- );
- },
- _ => (),
- }
- }
+ all_odd &= ALLOWED_ODD_FUNCTIONS
+ .iter()
+ .any(|odd_function| **odd_function == *path_segment_str);
+ arg = args.first().expect("A method always has a receiver.");
+ }
+
+ if_chain! {
+ if !all_odd;
+ if let ExprKind::Lit(lit) = &arg.kind;
+ if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ PRECEDENCE,
+ expr.span,
+ "unary minus has lower precedence than method call",
+ "consider adding parentheses to clarify your intent",
+ format!(
+ "-({})",
+ snippet_with_applicability(cx, operand.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
}
}
}
/// argument may also fail to compile if you change the argument. Applying
/// this lint on them will fix the problem, but they may be in other crates.
///
+ /// One notable example of a function that may cause issues, and which cannot
+ /// easily be changed due to being in the standard library is `Vec::contains`.
+ /// when called on a `Vec<Vec<T>>`. If a `&Vec` is passed to that method then
+ /// it will compile, but if a `&[T]` is passed then it will not compile.
+ ///
+ /// ```ignore
+ /// fn cannot_take_a_slice(v: &Vec<u8>) -> bool {
+ /// let vec_of_vecs: Vec<Vec<u8>> = some_other_fn();
+ ///
+ /// vec_of_vecs.contains(v)
+ /// }
+ /// ```
+ ///
/// Also there may be `fn(&Vec)`-typed references pointing to your function.
/// If you have them, you will get a compiler error after applying this lint's
/// suggestions. You then have the choice to undo your changes or change the
/// type of the reference.
///
/// Note that if the function is part of your public interface, there may be
- /// other crates referencing it you may not be aware. Carefully deprecate the
- /// function before applying the lint suggestions in this case.
+ /// other crates referencing it, of which you may not be aware. Carefully
+ /// deprecate the function before applying the lint suggestions in this case.
///
/// **Example:**
/// ```ignore
use crate::utils::sugg::Sugg;
use crate::utils::{
- higher, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability,
- span_lint_and_sugg, SpanlessEq,
+ eq_expr_value, higher, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability,
+ span_lint_and_sugg,
};
declare_clippy_lint! {
if let ExprKind::Block(block, None) = &else_.kind;
if block.stmts.is_empty();
if let Some(block_expr) = &block.expr;
- if SpanlessEq::new(cx).ignore_fn().eq_expr(subject, block_expr);
+ if eq_expr_value(cx, subject, block_expr);
then {
replacement = Some(format!("Some({}?)", receiver_str));
}
cx,
REDUNDANT_CLOSURE_CALL,
expr.span,
- "try not to call a closure in the expression where it is declared.",
+ "try not to call a closure in the expression where it is declared",
|diag| {
if decl.inputs.is_empty() {
let mut app = Applicability::MachineApplicable;
impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
- fn count_closure_usage<'tcx>(block: &'tcx hir::Block<'_>, path: &'tcx hir::Path<'tcx>) -> usize {
- struct ClosureUsageCount<'tcx> {
+ fn count_closure_usage<'a, 'tcx>(
+ cx: &'a LateContext<'tcx>,
+ block: &'tcx hir::Block<'_>,
+ path: &'tcx hir::Path<'tcx>,
+ ) -> usize {
+ struct ClosureUsageCount<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
path: &'tcx hir::Path<'tcx>,
count: usize,
};
- impl<'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'tcx> {
+ impl<'a, 'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
}
fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap<Self::Map> {
- hir_visit::NestedVisitorMap::None
+ hir_visit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
};
- let mut closure_usage_count = ClosureUsageCount { path, count: 0 };
+ let mut closure_usage_count = ClosureUsageCount { cx, path, count: 0 };
closure_usage_count.visit_block(block);
closure_usage_count.count
}
if let hir::ExprKind::Call(ref closure, _) = call.kind;
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = closure.kind;
if ident == path.segments[0].ident;
- if count_closure_usage(block, path) == 1;
+ if count_closure_usage(cx, block, path) == 1;
then {
span_lint(
cx,
impl<'tcx> LateLintPass<'tcx> for RepeatOnce {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) {
if_chain! {
- if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind;
+ if let ExprKind::MethodCall(path, _, [receiver, count], _) = &expr.kind;
if path.ident.name == sym!(repeat);
- if let Some(Constant::Int(1)) = constant_context(cx, cx.typeck_results()).expr(&args[1]);
- if !in_macro(args[0].span);
+ if let Some(Constant::Int(1)) = constant_context(cx, cx.typeck_results()).expr(&count);
+ if !in_macro(receiver.span);
then {
- let ty = walk_ptrs_ty(cx.typeck_results().expr_ty(&args[0]));
+ let ty = walk_ptrs_ty(cx.typeck_results().expr_ty(&receiver));
if ty.is_str() {
span_lint_and_sugg(
cx,
expr.span,
"calling `repeat(1)` on str",
"consider using `.to_string()` instead",
- format!("{}.to_string()", snippet(cx, args[0].span, r#""...""#)),
+ format!("{}.to_string()", snippet(cx, receiver.span, r#""...""#)),
Applicability::MachineApplicable,
);
} else if ty.builtin_index().is_some() {
expr.span,
"calling `repeat(1)` on slice",
"consider using `.to_vec()` instead",
- format!("{}.to_vec()", snippet(cx, args[0].span, r#""...""#)),
+ format!("{}.to_vec()", snippet(cx, receiver.span, r#""...""#)),
Applicability::MachineApplicable,
);
} else if is_type_diagnostic_item(cx, ty, sym!(string_type)) {
expr.span,
"calling `repeat(1)` on a string literal",
"consider using `.clone()` instead",
- format!("{}.clone()", snippet(cx, args[0].span, r#""...""#)),
+ format!("{}.clone()", snippet(cx, receiver.span, r#""...""#)),
Applicability::MachineApplicable,
);
}
use if_chain::if_chain;
-use rustc_ast::ast;
-use rustc_ast::visit::FnKind;
+use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
-use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
-use rustc_span::BytePos;
-use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then};
+use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_sugg, span_lint_and_then};
declare_clippy_lint! {
- /// **What it does:** Checks for return statements at the end of a block.
+ /// **What it does:** Checks for `let`-bindings, which are subsequently
+ /// returned.
///
- /// **Why is this bad?** Removing the `return` and semicolon will make the code
+ /// **Why is this bad?** It is just extraneous code. Remove it to make your code
/// more rusty.
///
- /// **Known problems:** If the computation returning the value borrows a local
- /// variable, removing the `return` may run afoul of the borrow checker.
+ /// **Known problems:** None.
///
/// **Example:**
/// ```rust
- /// fn foo(x: usize) -> usize {
- /// return x;
+ /// fn foo() -> String {
+ /// let x = String::new();
+ /// x
/// }
/// ```
- /// simplify to
- /// ```rust
- /// fn foo(x: usize) -> usize {
- /// x
+ /// instead, use
+ /// ```
+ /// fn foo() -> String {
+ /// String::new()
/// }
/// ```
- pub NEEDLESS_RETURN,
+ pub LET_AND_RETURN,
style,
- "using a return statement like `return expr;` where an expression would suffice"
+ "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
}
declare_clippy_lint! {
- /// **What it does:** Checks for unit (`()`) expressions that can be removed.
+ /// **What it does:** Checks for return statements at the end of a block.
///
- /// **Why is this bad?** Such expressions add no value, but can make the code
- /// less readable. Depending on formatting they can make a `break` or `return`
- /// statement look like a function call.
+ /// **Why is this bad?** Removing the `return` and semicolon will make the code
+ /// more rusty.
///
- /// **Known problems:** The lint currently misses unit return types in types,
- /// e.g., the `F` in `fn generic_unit<F: Fn() -> ()>(f: F) { .. }`.
+ /// **Known problems:** None.
///
/// **Example:**
/// ```rust
- /// fn return_unit() -> () {
- /// ()
+ /// fn foo(x: usize) -> usize {
+ /// return x;
/// }
/// ```
- pub UNUSED_UNIT,
+ /// simplify to
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ pub NEEDLESS_RETURN,
style,
- "needless unit expression"
+ "using a return statement like `return expr;` where an expression would suffice"
}
#[derive(PartialEq, Eq, Copy, Clone)]
Block,
}
-declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]);
+declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]);
-impl Return {
- // Check the final stmt or expr in a block for unnecessary return.
- fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
- if let Some(stmt) = block.stmts.last() {
- match stmt.kind {
- ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
- self.check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
- },
- _ => (),
+impl<'tcx> LateLintPass<'tcx> for Return {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ // we need both a let-binding stmt and an expr
+ if_chain! {
+ if let Some(retexpr) = block.expr;
+ if let Some(stmt) = block.stmts.iter().last();
+ if let StmtKind::Local(local) = &stmt.kind;
+ if local.ty.is_none();
+ if local.attrs.is_empty();
+ if let Some(initexpr) = &local.init;
+ if let PatKind::Binding(.., ident, _) = local.pat.kind;
+ if let ExprKind::Path(qpath) = &retexpr.kind;
+ if match_qpath(qpath, &[&*ident.name.as_str()]);
+ if !last_statement_borrows(cx, initexpr);
+ if !in_external_macro(cx.sess(), initexpr.span);
+ if !in_external_macro(cx.sess(), retexpr.span);
+ if !in_external_macro(cx.sess(), local.span);
+ if !in_macro(local.span);
+ then {
+ span_lint_and_then(
+ cx,
+ LET_AND_RETURN,
+ retexpr.span,
+ "returning the result of a `let` binding from a block",
+ |err| {
+ err.span_label(local.span, "unnecessary `let` binding");
+
+ if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
+ if !cx.typeck_results().expr_adjustments(&retexpr).is_empty() {
+ snippet.push_str(" as _");
+ }
+ err.multipart_suggestion(
+ "return the expression directly",
+ vec![
+ (local.span, String::new()),
+ (retexpr.span, snippet),
+ ],
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_help(initexpr.span, "this expression can be directly returned");
+ }
+ },
+ );
}
}
}
- // Check the final expression in a block if it's a return.
- fn check_final_expr(
+ fn check_fn(
&mut self,
- cx: &EarlyContext<'_>,
- expr: &ast::Expr,
- span: Option<Span>,
- replacement: RetReplacement,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ _: &'tcx FnDecl<'tcx>,
+ body: &'tcx Body<'tcx>,
+ _: Span,
+ _: HirId,
) {
- match expr.kind {
- // simple return is always "bad"
- ast::ExprKind::Ret(ref inner) => {
- // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
- if !expr.attrs.iter().any(attr_is_cfg) {
- Self::emit_return_lint(
- cx,
- span.expect("`else return` is not possible"),
- inner.as_ref().map(|i| i.span),
- replacement,
- );
- }
- },
- // a whole block? check it!
- ast::ExprKind::Block(ref block, _) => {
- self.check_block_return(cx, block);
- },
- // an if/if let expr, check both exprs
- // note, if without else is going to be a type checking error anyways
- // (except for unit type functions) so we don't match it
- ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => {
- self.check_block_return(cx, ifblock);
- self.check_final_expr(cx, elsexpr, None, RetReplacement::Empty);
- },
- // a match expr, check all arms
- ast::ExprKind::Match(_, ref arms) => {
- for arm in arms {
- self.check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block);
+ match kind {
+ FnKind::Closure(_) => check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty),
+ FnKind::ItemFn(..) | FnKind::Method(..) => {
+ if let ExprKind::Block(ref block, _) = body.value.kind {
+ check_block_return(cx, block);
}
},
- _ => (),
}
}
+}
- fn emit_return_lint(cx: &EarlyContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
- match inner_span {
- Some(inner_span) => {
- if in_external_macro(cx.sess(), inner_span) || inner_span.from_expansion() {
- return;
- }
+fn attr_is_cfg(attr: &Attribute) -> bool {
+ attr.meta_item_list().is_some() && attr.has_name(sym!(cfg))
+}
- span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
- if let Some(snippet) = snippet_opt(cx, inner_span) {
- diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
- }
- })
+fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ if let Some(expr) = block.expr {
+ check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty);
+ } else if let Some(stmt) = block.stmts.iter().last() {
+ match stmt.kind {
+ StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => {
+ check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
},
- None => match replacement {
- RetReplacement::Empty => {
- span_lint_and_sugg(
- cx,
- NEEDLESS_RETURN,
- ret_span,
- "unneeded `return` statement",
- "remove `return`",
- String::new(),
- Applicability::MachineApplicable,
- );
- },
- RetReplacement::Block => {
- span_lint_and_sugg(
+ _ => (),
+ }
+ }
+}
+
+fn check_final_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ span: Option<Span>,
+ replacement: RetReplacement,
+) {
+ match expr.kind {
+ // simple return is always "bad"
+ ExprKind::Ret(ref inner) => {
+ // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
+ if !expr.attrs.iter().any(attr_is_cfg) {
+ let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
+ if !borrows {
+ emit_return_lint(
cx,
- NEEDLESS_RETURN,
- ret_span,
- "unneeded `return` statement",
- "replace `return` with an empty block",
- "{}".to_string(),
- Applicability::MachineApplicable,
+ span.expect("`else return` is not possible"),
+ inner.as_ref().map(|i| i.span),
+ replacement,
);
- },
+ }
+ }
+ },
+ // a whole block? check it!
+ ExprKind::Block(ref block, _) => {
+ check_block_return(cx, block);
+ },
+ // a match expr, check all arms
+ // an if/if let expr, check both exprs
+ // note, if without else is going to be a type checking error anyways
+ // (except for unit type functions) so we don't match it
+ ExprKind::Match(_, ref arms, source) => match source {
+ MatchSource::Normal => {
+ for arm in arms.iter() {
+ check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block);
+ }
},
- }
+ MatchSource::IfDesugar {
+ contains_else_clause: true,
+ }
+ | MatchSource::IfLetDesugar {
+ contains_else_clause: true,
+ } => {
+ if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind {
+ check_block_return(cx, ifblock);
+ }
+ check_final_expr(cx, arms[1].body, None, RetReplacement::Empty);
+ },
+ _ => (),
+ },
+ _ => (),
}
}
-impl EarlyLintPass for Return {
- fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
- match kind {
- FnKind::Fn(.., Some(block)) => self.check_block_return(cx, block),
- FnKind::Closure(_, body) => self.check_final_expr(cx, body, Some(body.span), RetReplacement::Empty),
- FnKind::Fn(.., None) => {},
- }
- if_chain! {
- if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
- if let ast::TyKind::Tup(ref vals) = ty.kind;
- if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
- then {
- lint_unneeded_unit_return(cx, ty, span);
+fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
+ match inner_span {
+ Some(inner_span) => {
+ if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() {
+ return;
}
- }
- }
- fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
- if_chain! {
- if let Some(ref stmt) = block.stmts.last();
- if let ast::StmtKind::Expr(ref expr) = stmt.kind;
- if is_unit_expr(expr) && !stmt.span.from_expansion();
- then {
- let sp = expr.span;
+ span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
+ if let Some(snippet) = snippet_opt(cx, inner_span) {
+ diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
+ }
+ })
+ },
+ None => match replacement {
+ RetReplacement::Empty => {
span_lint_and_sugg(
cx,
- UNUSED_UNIT,
- sp,
- "unneeded unit expression",
- "remove the final `()`",
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "remove `return`",
String::new(),
Applicability::MachineApplicable,
);
- }
- }
- }
-
- fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
- match e.kind {
- ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
- if is_unit_expr(expr) && !expr.span.from_expansion() {
- span_lint_and_sugg(
- cx,
- UNUSED_UNIT,
- expr.span,
- "unneeded `()`",
- "remove the `()`",
- String::new(),
- Applicability::MachineApplicable,
- );
- }
},
- _ => (),
- }
- }
-
- fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
- let segments = &poly.trait_ref.path.segments;
-
- if_chain! {
- if segments.len() == 1;
- if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
- if let Some(args) = &segments[0].args;
- if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
- if let ast::FnRetTy::Ty(ty) = &generic_args.output;
- if ty.kind.is_unit();
- then {
- lint_unneeded_unit_return(cx, ty, generic_args.span);
- }
- }
+ RetReplacement::Block => {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "replace `return` with an empty block",
+ "{}".to_string(),
+ Applicability::MachineApplicable,
+ );
+ },
+ },
}
}
-fn attr_is_cfg(attr: &ast::Attribute) -> bool {
- attr.meta_item_list().is_some() && attr.has_name(sym!(cfg))
+fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ let mut visitor = BorrowVisitor { cx, borrows: false };
+ walk_expr(&mut visitor, expr);
+ visitor.borrows
}
-// get the def site
-#[must_use]
-fn get_def(span: Span) -> Option<Span> {
- if span.from_expansion() {
- Some(span.ctxt().outer_expn_data().def_site)
- } else {
- None
- }
+struct BorrowVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ borrows: bool,
}
-// is this expr a `()` unit?
-fn is_unit_expr(expr: &ast::Expr) -> bool {
- if let ast::ExprKind::Tup(ref vals) = expr.kind {
- vals.is_empty()
- } else {
- false
+impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.borrows {
+ return;
+ }
+
+ if let Some(def_id) = fn_def_id(self.cx, expr) {
+ self.borrows = self
+ .cx
+ .tcx
+ .fn_sig(def_id)
+ .output()
+ .skip_binder()
+ .walk()
+ .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
+ }
+
+ walk_expr(self, expr);
}
-}
-fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
- let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
- fn_source
- .rfind("->")
- .map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
- (
- #[allow(clippy::cast_possible_truncation)]
- ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
- Applicability::MachineApplicable,
- )
- })
- } else {
- (ty.span, Applicability::MaybeIncorrect)
- };
- span_lint_and_sugg(
- cx,
- UNUSED_UNIT,
- ret_span,
- "unneeded unit return type",
- "remove the `-> ()`",
- String::new(),
- appl,
- );
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
}
--- /dev/null
+use crate::utils::{eq_expr_value, snippet, span_lint};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for explicit self-assignments.
+ ///
+ /// **Why is this bad?** Self-assignments are redundant and unlikely to be
+ /// intentional.
+ ///
+ /// **Known problems:** If expression contains any deref coercions or
+ /// indexing operations they are assumed not to have any side effects.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// struct Event {
+ /// id: usize,
+ /// x: i32,
+ /// y: i32,
+ /// }
+ ///
+ /// fn copy_position(a: &mut Event, b: &Event) {
+ /// a.x = b.x;
+ /// a.y = a.y;
+ /// }
+ /// ```
+ pub SELF_ASSIGNMENT,
+ correctness,
+ "explicit self-assignment"
+}
+
+declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]);
+
+impl<'tcx> LateLintPass<'tcx> for SelfAssignment {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Assign(lhs, rhs, _) = &expr.kind {
+ if eq_expr_value(cx, lhs, rhs) {
+ let lhs = snippet(cx, lhs.span, "<lhs>");
+ let rhs = snippet(cx, rhs.span, "<rhs>");
+ span_lint(
+ cx,
+ SELF_ASSIGNMENT,
+ expr.span,
+ &format!("self-assignment of `{}` to `{}`", rhs, lhs),
+ );
+ }
+ }
+ }
+}
slice_name: String,
method: SortingKind,
method_args: String,
+ slice_type: String,
}
fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> {
if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
if let Some(slice) = &args.get(0);
if let Some(method) = SortingKind::from_stable_name(&method_name.ident.name.as_str());
- if is_slice_of_primitives(cx, slice);
+ if let Some(slice_type) = is_slice_of_primitives(cx, slice);
then {
let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", ");
- Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str })
+ Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type })
} else {
None
}
STABLE_SORT_PRIMITIVE,
expr.span,
format!(
- "Use {} instead of {}",
+ "used {} instead of {} to sort primitive type `{}`",
+ detection.method.stable_name(),
detection.method.unstable_name(),
- detection.method.stable_name()
+ detection.slice_type,
)
.as_str(),
"try",
cx,
expr,
binop.node,
- &["Add", "Sub", "Mul", "Div"],
+ &[
+ "Add", "Sub", "Mul", "Div", "Rem", "BitAnd", "BitOr", "BitXor", "Shl", "Shr",
+ ],
&[
hir::BinOpKind::Add,
hir::BinOpKind::Sub,
hir::BinOpKind::Mul,
hir::BinOpKind::Div,
+ hir::BinOpKind::Rem,
+ hir::BinOpKind::BitAnd,
+ hir::BinOpKind::BitOr,
+ hir::BinOpKind::BitXor,
+ hir::BinOpKind::Shl,
+ hir::BinOpKind::Shr,
],
) {
span_lint(
use crate::utils::sugg::Sugg;
use crate::utils::{
- differing_macro_contexts, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty,
- SpanlessEq,
+ differing_macro_contexts, eq_expr_value, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then,
+ walk_ptrs_ty,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
if rhs2.segments.len() == 1;
if ident.as_str() == rhs2.segments[0].ident.as_str();
- if SpanlessEq::new(cx).ignore_fn().eq_expr(tmp_init, lhs1);
- if SpanlessEq::new(cx).ignore_fn().eq_expr(rhs1, lhs2);
+ if eq_expr_value(cx, tmp_init, lhs1);
+ if eq_expr_value(cx, rhs1, lhs2);
then {
if let ExprKind::Field(ref lhs1, _) = lhs1.kind {
if let ExprKind::Field(ref lhs2, _) = lhs2.kind {
fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> {
if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind {
if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind {
- if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, lhs2) {
+ if eq_expr_value(cx, lhs1, lhs2) {
let ty = walk_ptrs_ty(cx.typeck_results().expr_ty(lhs1));
if matches!(ty.kind, ty::Slice(_))
if !differing_macro_contexts(first.span, second.span);
if let ExprKind::Assign(ref lhs0, ref rhs0, _) = first.kind;
if let ExprKind::Assign(ref lhs1, ref rhs1, _) = second.kind;
- if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs0, rhs1);
- if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, rhs0);
+ if eq_expr_value(cx, lhs0, rhs1);
+ if eq_expr_value(cx, lhs1, rhs0);
then {
let lhs0 = Sugg::hir_opt(cx, lhs0);
let rhs0 = Sugg::hir_opt(cx, rhs0);
--- /dev/null
+use crate::utils::{match_def_path, match_trait_method, paths, qpath_res, span_lint};
+use if_chain::if_chain;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, HirId, ImplItem, ImplItemKind, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for uses of `to_string()` in `Display` traits.
+ ///
+ /// **Why is this bad?** Usually `to_string` is implemented indirectly
+ /// via `Display`. Hence using it while implementing `Display` would
+ /// lead to infinite recursion.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.to_string())
+ /// }
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.0)
+ /// }
+ /// }
+ /// ```
+ pub TO_STRING_IN_DISPLAY,
+ correctness,
+ "`to_string` method used while implementing `Display` trait"
+}
+
+#[derive(Default)]
+pub struct ToStringInDisplay {
+ in_display_impl: bool,
+ self_hir_id: Option<HirId>,
+}
+
+impl ToStringInDisplay {
+ pub fn new() -> Self {
+ Self {
+ in_display_impl: false,
+ self_hir_id: None,
+ }
+ }
+}
+
+impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]);
+
+impl LateLintPass<'_> for ToStringInDisplay {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_display_impl(cx, item) {
+ self.in_display_impl = true;
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_display_impl(cx, item) {
+ self.in_display_impl = false;
+ self.self_hir_id = None;
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
+ if_chain! {
+ if self.in_display_impl;
+ if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
+ let body = cx.tcx.hir().body(*body_id);
+ if !body.params.is_empty();
+ then {
+ let self_param = &body.params[0];
+ self.self_hir_id = Some(self_param.pat.hir_id);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(ref path, _, args, _) = expr.kind;
+ if path.ident.name == sym!(to_string);
+ if match_trait_method(cx, expr, &paths::TO_STRING);
+ if self.in_display_impl;
+ if let ExprKind::Path(ref qpath) = args[0].kind;
+ if let Res::Local(hir_id) = qpath_res(cx, qpath, args[0].hir_id);
+ if let Some(self_hir_id) = self.self_hir_id;
+ if hir_id == self_hir_id;
+ then {
+ span_lint(
+ cx,
+ TO_STRING_IN_DISPLAY,
+ expr.span,
+ "using `to_string` in `fmt::Display` implementation might lead to infinite recursion",
+ );
+ }
+ }
+ }
+}
+
+fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if_chain! {
+ if let ItemKind::Impl { of_trait: Some(trait_ref), .. } = &item.kind;
+ if let Some(did) = trait_ref.trait_def_id();
+ then {
+ match_def_path(cx, did, &paths::DISPLAY_TRAIT)
+ } else {
+ false
+ }
+ }
+}
use crate::utils::{
- is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg,
+ in_constant, is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg,
span_lint_and_then, sugg,
};
use if_chain::if_chain;
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::TRANSMUTE);
then {
+ // Avoid suggesting from/to bits in const contexts.
+ // See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`.
+ let const_context = in_constant(cx, e.hir_id);
+
let from_ty = cx.typeck_results().expr_ty(&args[0]);
let to_ty = cx.typeck_results().expr_ty(e);
},
)
},
- (ty::Int(_) | ty::Uint(_), ty::Float(_)) => span_lint_and_then(
+ (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => span_lint_and_then(
cx,
TRANSMUTE_INT_TO_FLOAT,
e.span,
);
},
),
- (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) => span_lint_and_then(
+ (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => span_lint_and_then(
cx,
TRANSMUTE_FLOAT_TO_INT,
e.span,
use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg};
use if_chain::if_chain;
+use rustc_ast::attr;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
return;
}
for a in attrs {
- if a.meta_item_list().is_some() && a.has_name(sym!(proc_macro_derive)) {
- return;
+ if let Some(meta_items) = a.meta_item_list() {
+ if a.has_name(sym!(proc_macro_derive))
+ || (a.has_name(sym!(inline)) && attr::list_contains_name(&meta_items, sym!(always)))
+ {
+ return;
+ }
}
}
},
use crate::utils::{
- is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet,
- snippet_with_macro_callsite, span_lint_and_sugg,
+ is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet, snippet_with_macro_callsite,
+ span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind, QPath, LangItem, MatchSource};
+use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
);
return; // don't recurse into the type
}
- if let Some(span) = match_type_parameter(cx, qpath, &paths::BOX) {
+ if match_type_parameter(cx, qpath, &paths::BOX).is_some() {
+ let box_ty = match &last_path_segment(qpath).args.unwrap().args[0] {
+ GenericArg::Type(ty) => match &ty.kind {
+ TyKind::Path(qpath) => qpath,
+ _ => return,
+ },
+ _ => return,
+ };
+ let inner_span = match &last_path_segment(&box_ty).args.unwrap().args[0] {
+ GenericArg::Type(ty) => ty.span,
+ _ => return,
+ };
span_lint_and_sugg(
cx,
REDUNDANT_ALLOCATION,
hir_ty.span,
"usage of `Rc<Box<T>>`",
"try",
- snippet(cx, span, "..").to_string(),
+ format!("Rc<{}>", snippet(cx, inner_span, "..")),
Applicability::MachineApplicable,
);
return; // don't recurse into the type
use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path};
use crate::utils::{over, span_lint_and_then};
-use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
id: DUMMY_NODE_ID,
kind: Wild,
span: DUMMY_SP,
- tokens: None
+ tokens: None,
};
mem::replace(from, dummy)
}
--- /dev/null
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit::FnKind;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+use crate::utils::span_lint_and_sugg;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unit (`()`) expressions that can be removed.
+ ///
+ /// **Why is this bad?** Such expressions add no value, but can make the code
+ /// less readable. Depending on formatting they can make a `break` or `return`
+ /// statement look like a function call.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
+
+impl EarlyLintPass for UnusedUnit {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
+ lint_unneeded_unit_return(cx, ty, span);
+ }
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if_chain! {
+ if let Some(ref stmt) = block.stmts.last();
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind;
+ if is_unit_expr(expr) && !stmt.span.from_expansion();
+ then {
+ let sp = expr.span;
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ sp,
+ "unneeded unit expression",
+ "remove the final `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
+ if is_unit_expr(expr) && !expr.span.from_expansion() {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ expr.span,
+ "unneeded `()`",
+ "remove the `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
+ let segments = &poly.trait_ref.path.segments;
+
+ if_chain! {
+ if segments.len() == 1;
+ if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
+ if let Some(args) = &segments[0].args;
+ if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
+ if let ast::FnRetTy::Ty(ty) = &generic_args.output;
+ if ty.kind.is_unit();
+ then {
+ lint_unneeded_unit_return(cx, ty, generic_args.span);
+ }
+ }
+ }
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<Span> {
+ if span.from_expansion() {
+ Some(span.ctxt().outer_expn_data().def_site)
+ } else {
+ None
+ }
+}
+
+// is this expr a `()` unit?
+fn is_unit_expr(expr: &ast::Expr) -> bool {
+ if let ast::ExprKind::Tup(ref vals) = expr.kind {
+ vals.is_empty()
+ } else {
+ false
+ }
+}
+
+fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
+ let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
+ fn_source
+ .rfind("->")
+ .map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+ (
+ #[allow(clippy::cast_possible_truncation)]
+ ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+ Applicability::MachineApplicable,
+ )
+ })
+ } else {
+ (ty.span, Applicability::MaybeIncorrect)
+ };
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ ret_span,
+ "unneeded unit return type",
+ "remove the `-> ()`",
+ String::new(),
+ appl,
+ );
+}
--- /dev/null
+use crate::utils::{is_type_diagnostic_item, method_chain_args, return_ty, span_lint_and_then, walk_ptrs_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for functions of type Result that contain `expect()` or `unwrap()`
+ ///
+ /// **Why is this bad?** These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics.
+ ///
+ /// **Known problems:** This can cause false positives in functions that handle both recoverable and non recoverable errors.
+ ///
+ /// **Example:**
+ /// Before:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .expect("cannot divide the input by three");
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .map_err(|e| format!("cannot divide the input by three: {}", e))?;
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ pub UNWRAP_IN_RESULT,
+ restriction,
+ "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
+}
+
+declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if_chain! {
+ // first check if it's a method or function
+ if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind;
+ // checking if its return type is `result` or `option`
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(result_type))
+ || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(option_type));
+ then {
+ lint_impl_body(cx, impl_item.span, impl_item);
+ }
+ }
+ }
+}
+
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::{Expr, ImplItemKind};
+
+struct FindExpectUnwrap<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ result: Vec<Span>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // check for `expect`
+ if let Some(arglists) = method_chain_args(expr, &["expect"]) {
+ let reciever_ty = walk_ptrs_ty(self.typeck_results.expr_ty(&arglists[0][0]));
+ if is_type_diagnostic_item(self.lcx, reciever_ty, sym!(option_type))
+ || is_type_diagnostic_item(self.lcx, reciever_ty, sym!(result_type))
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let reciever_ty = walk_ptrs_ty(self.typeck_results.expr_ty(&arglists[0][0]));
+ if is_type_diagnostic_item(self.lcx, reciever_ty, sym!(option_type))
+ || is_type_diagnostic_item(self.lcx, reciever_ty, sym!(result_type))
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
+ if_chain! {
+
+ if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
+ then {
+ let body = cx.tcx.hir().body(body_id);
+ let impl_item_def_id = cx.tcx.hir().local_def_id(impl_item.hir_id);
+ let mut fpu = FindExpectUnwrap {
+ lcx: cx,
+ typeck_results: cx.tcx.typeck(impl_item_def_id),
+ result: Vec::new(),
+ };
+ fpu.visit_expr(&body.value);
+
+ // if we've found one, lint
+ if !fpu.result.is_empty() {
+ span_lint_and_then(
+ cx,
+ UNWRAP_IN_RESULT,
+ impl_span,
+ "used unwrap or expect in a function that returns result or option",
+ move |diag| {
+ diag.help(
+ "unwrap and expect should not be used in a function that returns result or option" );
+ diag.span_note(fpu.result, "potential non-recoverable error(s)");
+ });
+ }
+ }
+ }
+}
/// ```
pub USE_SELF,
nursery,
- "Unnecessary structure name repetition whereas `Self` is applicable"
+ "unnecessary structure name repetition whereas `Self` is applicable"
}
declare_lint_pass!(UseSelf => [USE_SELF]);
+use crate::utils::sugg::Sugg;
use crate::utils::{
get_parent_expr, is_type_diagnostic_item, match_def_path, match_trait_method, paths, snippet,
snippet_with_macro_callsite, span_lint_and_help, span_lint_and_sugg,
if TyS::same_type(a, b);
then {
- let sugg = snippet(cx, args[0].span.source_callsite(), "<expr>").into_owned();
+ let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "<expr>").maybe_par();
let sugg_msg =
format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
span_lint_and_sugg(
e.span,
"useless conversion to the same type",
&sugg_msg,
- sugg,
+ sugg.to_string(),
Applicability::MachineApplicable, // snippet
);
}
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::utils::{both, over};
-use rustc_ast::{self as ast, *};
use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
use std::mem;
}
fn print_qpath(&mut self, path: &QPath<'_>) {
- match *path {
- QPath::LangItem(lang_item, _) => {
- println!(
- " if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
- self.current, lang_item,
- );
- },
- _ => {
- print!(" if match_qpath({}, &[", self.current);
- print_path(path, &mut true);
- println!("]);");
- },
+ if let QPath::LangItem(lang_item, _) = *path {
+ println!(
+ " if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
+ self.current, lang_item,
+ );
+ } else {
+ print!(" if match_qpath({}, &[", self.current);
+ print_path(path, &mut true);
+ println!("]);");
}
}
}
(type_complexity_threshold, "type_complexity_threshold": u64, 250),
/// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
(single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
- /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
+ /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(too_large_for_stack, "too_large_for_stack": u64, 200),
/// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
}
match expr.kind {
- hir::ExprKind::Call(ref path, ref args) if matches!(
- path.kind,
- hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
- ) => Some(Range {
- start: Some(&args[0]),
- end: Some(&args[1]),
- limits: ast::RangeLimits::Closed,
- }),
- hir::ExprKind::Struct(ref path, ref fields, None) => {
- match path {
- hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
- start: None,
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::Closed,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- _ => None,
- }
+ hir::ExprKind::Call(ref path, ref args)
+ if matches!(
+ path.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
+ ) =>
+ {
+ Some(Range {
+ start: Some(&args[0]),
+ end: Some(&args[1]),
+ limits: ast::RangeLimits::Closed,
+ })
+ },
+ hir::ExprKind::Struct(ref path, ref fields, None) => match path {
+ hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
+ start: None,
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::Closed,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ _ => None,
},
_ => None,
}
use rustc_ast::ast::InlineAsmTemplatePiece;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_hir::{
- BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat,
- FnRetTy, GenericArg, GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName,
- Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
+ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat, FnRetTy,
+ GenericArg, GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
+ PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
};
use rustc_lint::LateContext;
use rustc_middle::ich::StableHashingContextProvider;
/// Context used to evaluate constant expressions.
cx: &'a LateContext<'tcx>,
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
- /// If is true, never consider as equal expressions containing function
- /// calls.
- ignore_fn: bool,
+ allow_side_effects: bool,
}
impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
Self {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
- ignore_fn: false,
+ allow_side_effects: true,
}
}
- pub fn ignore_fn(self) -> Self {
+ /// Consider expressions containing potential side effects as not equal.
+ pub fn deny_side_effects(self) -> Self {
Self {
- ignore_fn: true,
+ allow_side_effects: false,
..self
}
}
#[allow(clippy::similar_names)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
- if self.ignore_fn && differing_macro_contexts(left.span, right.span) {
+ if !self.allow_side_effects && differing_macro_contexts(left.span, right.span) {
return false;
}
both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str())
},
(&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
- self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ self.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
},
(&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
- lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ self.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
},
(&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
(&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
},
(&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
(&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
- !self.ignore_fn && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ self.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
},
(&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
| (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
})
},
(&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
- !self.ignore_fn && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ self.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
},
(&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(ll_id.body));
}
pub fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
- match (&left, &right) {
- (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) =>
- li.name.as_str() == ri.name.as_str() && self.eq_pat(lp, rp),
- }
+ let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
+ li.name.as_str() == ri.name.as_str() && self.eq_pat(lp, rp)
}
/// Checks whether two patterns are the same.
(&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
},
- (&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) =>
- llang_item == rlang_item,
+ (&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
_ => false,
}
}
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}
+/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
+pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
+}
+
/// Type used to hash an ast element. This is different from the `Hash` trait
/// on ast types as this
/// trait would consider IDs and spans.
},
QPath::LangItem(lang_item, ..) => {
lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
- }
+ },
}
// self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
}
},
QPath::LangItem(lang_item, ..) => {
lang_item.hash(&mut self.s);
- }
+ },
},
TyKind::OpaqueDef(_, arg_list) => {
self.hash_generic_args(arg_list);
-use crate::utils::SpanlessEq;
use crate::utils::{
is_expn_of, match_def_path, match_qpath, match_type, method_calls, paths, run_lints, snippet, span_lint,
- span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty,
+ span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty, SpanlessEq,
};
use if_chain::if_chain;
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId};
if let StmtKind::Semi(only_expr) = &stmts[0].kind;
if let ExprKind::MethodCall(ref ps, _, ref span_call_args, _) = &only_expr.kind;
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
- let mut sle = SpanlessEq::new(cx).ignore_fn();
+ let mut sle = SpanlessEq::new(cx).deny_side_effects();
then {
match &*ps.ident.as_str() {
"span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
pub mod usage;
pub use self::attrs::*;
pub use self::diagnostics::*;
-pub use self::hir_utils::{both, over, SpanlessEq, SpanlessHash};
+pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash};
use std::borrow::Cow;
use std::mem;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::map::Map;
-use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Ty, TyCtxt, TypeFoldable};
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
+use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable};
use rustc_mir::const_eval;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::original_sp;
}
/// Same as `snippet_block`, but adapts the applicability level by the rules of
-/// `snippet_with_applicabiliy`.
+/// `snippet_with_applicability`.
pub fn snippet_block_with_applicability<'a, T: LintContext>(
cx: &T,
span: Span,
cx.tcx.erase_late_bound_regions(&ret_ty)
}
+/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
+pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool {
+ ty.walk().any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
/// Returns `true` if the given type is an `unsafe` function.
pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match ty.kind {
}
}
-// check if expr is calling method or function with #[must_use] attribyte
+// check if expr is calling method or function with #[must_use] attribute
pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let did = match expr.kind {
ExprKind::Call(ref path, _) => if_chain! {
}
}
-/// Returns true iff the given expression is a slice of primitives (as defined in the
-/// `is_recursively_primitive_type` function).
-pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+/// Returns Option<String> where String is a textual representation of the type encapsulated in the
+/// slice iff the given expression is a slice of primitives (as defined in the
+/// `is_recursively_primitive_type` function) and None otherwise.
+pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
- match expr_type.kind {
+ let expr_kind = &expr_type.kind;
+ let is_primitive = match expr_kind {
ty::Slice(ref element_type)
| ty::Ref(
_,
_,
) => is_recursively_primitive_type(element_type),
_ => false,
+ };
+
+ if is_primitive {
+ // if we have wrappers like Array, Slice or Tuple, print these
+ // and get the type enclosed in the slice ref
+ match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind {
+ ty::Slice(..) => return Some("slice".into()),
+ ty::Array(..) => return Some("array".into()),
+ ty::Tuple(..) => return Some("tuple".into()),
+ _ => {
+ // is_recursively_primitive_type() should have taken care
+ // of the rest and we can rely on the type that is found
+ let refs_peeled = expr_type.peel_refs();
+ return Some(refs_peeled.walk().last().unwrap().to_string());
+ },
+ }
}
+ None
}
#[macro_export]
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 2] = ["f32", "EPSILON"];
+pub const F64_EPSILON: [&str; 2] = ["f64", "EPSILON"];
pub const FILE: [&str; 3] = ["std", "fs", "File"];
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
-use crate::consts::constant;
+use crate::consts::{constant, Constant};
+use crate::rustc_target::abi::LayoutOf;
use crate::utils::{higher, is_copy, snippet_with_applicability, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
+#[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct UselessVec {
+ pub too_large_for_stack: u64,
+}
+
declare_clippy_lint! {
/// **What it does:** Checks for usage of `&vec![..]` when using `&[..]` would
/// be possible.
"useless `vec!`"
}
-declare_lint_pass!(UselessVec => [USELESS_VEC]);
+impl_lint_pass!(UselessVec => [USELESS_VEC]);
impl<'tcx> LateLintPass<'tcx> for UselessVec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref addressee) = expr.kind;
if let Some(vec_args) = higher::vec_macro(cx, addressee);
then {
- check_vec_macro(cx, &vec_args, expr.span);
+ self.check_vec_macro(cx, &vec_args, expr.span);
}
}
.ctxt()
.outer_expn_data()
.call_site;
- check_vec_macro(cx, &vec_args, span);
+ self.check_vec_macro(cx, &vec_args, span);
}
}
}
}
-fn check_vec_macro<'tcx>(cx: &LateContext<'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) {
- let mut applicability = Applicability::MachineApplicable;
- let snippet = match *vec_args {
- higher::VecArgs::Repeat(elem, len) => {
- if constant(cx, cx.typeck_results(), len).is_some() {
- format!(
- "&[{}; {}]",
- snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
- snippet_with_applicability(cx, len.span, "len", &mut applicability)
- )
- } else {
- return;
- }
- },
- higher::VecArgs::Vec(args) => {
- if let Some(last) = args.iter().last() {
- let span = args[0].span.to(last.span);
+impl UselessVec {
+ fn check_vec_macro<'tcx>(self, cx: &LateContext<'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = match *vec_args {
+ higher::VecArgs::Repeat(elem, len) => {
+ if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
+ #[allow(clippy::cast_possible_truncation)]
+ if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
+ return;
+ }
- format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
- } else {
- "&[]".into()
- }
- },
- };
+ format!(
+ "&[{}; {}]",
+ snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
+ snippet_with_applicability(cx, len.span, "len", &mut applicability)
+ )
+ } else {
+ return;
+ }
+ },
+ higher::VecArgs::Vec(args) => {
+ if let Some(last) = args.iter().last() {
+ #[allow(clippy::cast_possible_truncation)]
+ if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
+ return;
+ }
+ let span = args[0].span.to(last.span);
+
+ format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
+ } else {
+ "&[]".into()
+ }
+ },
+ };
+
+ span_lint_and_sugg(
+ cx,
+ USELESS_VEC,
+ span,
+ "useless use of `vec!`",
+ "you can use a slice directly",
+ snippet,
+ applicability,
+ );
+ }
+}
- span_lint_and_sugg(
- cx,
- USELESS_VEC,
- span,
- "useless use of `vec!`",
- "you can use a slice directly",
- snippet,
- applicability,
- );
+fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
+ let ty = cx.typeck_results().expr_ty_adjusted(expr);
+ cx.layout_of(ty).map_or(0, |l| l.size.bytes())
}
/// Returns the item type of the vector (i.e., the `T` in `Vec<T>`).
}
}
-// Allow "...prelude::*" imports.
+// Allow "...prelude::..::*" imports.
// Many crates have a prelude, and it is imported as a glob by design.
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
- segments
- .iter()
- .last()
- .map_or(false, |ps| ps.ident.as_str() == "prelude")
+ segments.iter().any(|ps| ps.ident.as_str() == "prelude")
}
// Allow "super::*" imports in tests.
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
if mac.path == sym!(println) {
span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
- if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
if fmt_str.symbol == Symbol::intern("") {
span_lint_and_sugg(
cx,
}
} else if mac.path == sym!(print) {
span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
- if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
if check_newlines(&fmt_str) {
span_lint_and_then(
cx,
}
}
} else if mac.path == sym!(write) {
- if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) {
if check_newlines(&fmt_str) {
span_lint_and_then(
cx,
}
}
} else if mac.path == sym!(writeln) {
- if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+ if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
if fmt_str.symbol == Symbol::intern("") {
let mut applicability = Applicability::MachineApplicable;
- let suggestion = expr.map_or_else(
- || {
- applicability = Applicability::HasPlaceholders;
- Cow::Borrowed("v")
- },
- |e| snippet_with_applicability(cx, e.span, "v", &mut Applicability::MachineApplicable),
- );
+ // FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed
+ #[allow(clippy::option_if_let_else)]
+ let suggestion = if let Some(e) = expr {
+ snippet_with_applicability(cx, e.span, "v", &mut applicability)
+ } else {
+ applicability = Applicability::HasPlaceholders;
+ Cow::Borrowed("v")
+ };
span_lint_and_sugg(
cx,
/// (Some("string to write: {}"), Some(buf))
/// ```
#[allow(clippy::too_many_lines)]
- fn check_tts<'a>(
- &self,
- cx: &EarlyContext<'a>,
- tts: &TokenStream,
- is_write: bool,
- ) -> (Option<StrLit>, Option<Expr>) {
+ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) {
use rustc_parse_format::{
AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
Piece,
};
- let tts = tts.clone();
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
let mut expr: Option<Expr> = None;
Running our UI test should now produce output that contains the lint message.
+According to [the rustc-dev-guide], the text should be matter of fact and avoid
+capitalization and periods, unless multiple sentences are needed.
+When code or an identifier must appear in a message or label, it should be
+surrounded with single acute accents \`.
+
[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn
[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/diagnostics.rs
+[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html
## Adding the lint logic
deprecation: None,
module: "misc",
},
+ Lint {
+ name: "float_equality_without_abs",
+ group: "correctness",
+ desc: "float equality check without `.abs()`",
+ deprecation: None,
+ module: "float_equality_without_abs",
+ },
Lint {
name: "fn_address_comparisons",
group: "correctness",
group: "style",
desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block",
deprecation: None,
- module: "let_and_return",
+ module: "returns",
},
Lint {
name: "let_underscore_lock",
deprecation: None,
module: "methods",
},
+ Lint {
+ name: "self_assignment",
+ group: "correctness",
+ desc: "explicit self-assignment",
+ deprecation: None,
+ module: "self_assignment",
+ },
Lint {
name: "serde_api_misuse",
group: "correctness",
deprecation: None,
module: "methods",
},
+ Lint {
+ name: "single_char_push_str",
+ group: "style",
+ desc: "`push_str()` used with a single-character string literal as parameter",
+ deprecation: None,
+ module: "methods",
+ },
Lint {
name: "single_component_path_imports",
group: "style",
deprecation: None,
module: "to_digit_is_some",
},
+ Lint {
+ name: "to_string_in_display",
+ group: "correctness",
+ desc: "`to_string` method used while implementing `Display` trait",
+ deprecation: None,
+ module: "to_string_in_display",
+ },
Lint {
name: "todo",
group: "restriction",
deprecation: None,
module: "methods",
},
+ Lint {
+ name: "unnecessary_lazy_evaluations",
+ group: "style",
+ desc: "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation",
+ deprecation: None,
+ module: "methods",
+ },
Lint {
name: "unnecessary_mut_passed",
group: "style",
group: "style",
desc: "needless unit expression",
deprecation: None,
- module: "returns",
+ module: "unused_unit",
+ },
+ Lint {
+ name: "unwrap_in_result",
+ group: "restriction",
+ desc: "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`",
+ deprecation: None,
+ module: "unwrap_in_result",
},
Lint {
name: "unwrap_used",
Lint {
name: "use_self",
group: "nursery",
- desc: "Unnecessary structure name repetition whereas `Self` is applicable",
+ desc: "unnecessary structure name repetition whereas `Self` is applicable",
deprecation: None,
module: "use_self",
},
return;
}
- // Skip this test if rustup nightly is unavailable
+ // Skip this test if nightly rustfmt is unavailable
let rustup_output = Command::new("rustup")
.args(&["component", "list", "--toolchain", "nightly"])
.output()
}
let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- let dev_dir = root_dir.join("clippy_dev");
- let target_dir = root_dir.join("target");
- let target_dir = target_dir.to_str().unwrap();
let output = Command::new("cargo")
- .current_dir(dev_dir)
- .args(&["+nightly", "run", "--target-dir", target_dir, "--", "fmt", "--check"])
+ .current_dir(root_dir)
+ .args(&["dev", "fmt", "--check"])
.output()
.unwrap();
A,
}
}
+
+pub mod prelude {
+ pub mod v1 {
+ pub struct PreludeModAnywhere;
+ }
+}
#![allow(clippy::declare_interior_mutable_const, clippy::ref_in_deref)]
use std::borrow::Cow;
-use std::cell::Cell;
+use std::cell::{Cell, UnsafeCell};
use std::fmt::Display;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Once;
const ATOMIC: AtomicUsize = AtomicUsize::new(9);
}
+// This is just a pointer that can be safely dereferended,
+// it's semantically the same as `&'static T`;
+// but it isn't allowed to make a static reference from an arbitrary integer value at the moment.
+// For more information, please see the issue #5918.
+pub struct StaticRef<T> {
+ ptr: *const T,
+}
+
+impl<T> StaticRef<T> {
+ /// Create a new `StaticRef` from a raw pointer
+ ///
+ /// ## Safety
+ ///
+ /// Callers must pass in a reference to statically allocated memory which
+ /// does not overlap with other values.
+ pub const unsafe fn new(ptr: *const T) -> StaticRef<T> {
+ StaticRef { ptr }
+ }
+}
+
+impl<T> std::ops::Deref for StaticRef<T> {
+ type Target = T;
+
+ fn deref(&self) -> &'static T {
+ unsafe { &*self.ptr }
+ }
+}
+
+// use a tuple to make sure referencing a field behind a pointer isn't linted.
+const CELL_REF: StaticRef<(UnsafeCell<u32>,)> = unsafe { StaticRef::new(std::ptr::null()) };
+
fn main() {
ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability
assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability
assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability
assert_eq!(NO_ANN.to_string(), "70"); // should never lint this.
+
+ let _ = &CELL_REF.0;
}
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:34:5
+ --> $DIR/borrow_interior_mutable_const.rs:65:5
|
LL | ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability
| ^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:35:16
+ --> $DIR/borrow_interior_mutable_const.rs:66:16
|
LL | assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability
| ^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:38:22
+ --> $DIR/borrow_interior_mutable_const.rs:69:22
|
LL | let _once_ref = &ONCE_INIT; //~ ERROR interior mutability
| ^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:39:25
+ --> $DIR/borrow_interior_mutable_const.rs:70:25
|
LL | let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability
| ^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:40:27
+ --> $DIR/borrow_interior_mutable_const.rs:71:27
|
LL | let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability
| ^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:41:26
+ --> $DIR/borrow_interior_mutable_const.rs:72:26
|
LL | let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability
| ^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:52:14
+ --> $DIR/borrow_interior_mutable_const.rs:83:14
|
LL | let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:53:14
+ --> $DIR/borrow_interior_mutable_const.rs:84:14
|
LL | let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:54:19
+ --> $DIR/borrow_interior_mutable_const.rs:85:19
|
LL | let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:55:14
+ --> $DIR/borrow_interior_mutable_const.rs:86:14
|
LL | let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:56:13
+ --> $DIR/borrow_interior_mutable_const.rs:87:13
|
LL | let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:62:13
+ --> $DIR/borrow_interior_mutable_const.rs:93:13
|
LL | let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability
| ^^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:67:5
+ --> $DIR/borrow_interior_mutable_const.rs:98:5
|
LL | CELL.set(2); //~ ERROR interior mutability
| ^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:68:16
+ --> $DIR/borrow_interior_mutable_const.rs:99:16
|
LL | assert_eq!(CELL.get(), 6); //~ ERROR interior mutability
| ^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:81:5
+ --> $DIR/borrow_interior_mutable_const.rs:112:5
|
LL | u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability
| ^^^^^^^^^^^
= help: assign this const to a local or static variable, and use the variable here
error: a `const` item with interior mutability should not be borrowed
- --> $DIR/borrow_interior_mutable_const.rs:82:16
+ --> $DIR/borrow_interior_mutable_const.rs:113:16
|
LL | assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability
| ^^^^^^^^^^^
--- /dev/null
+#![warn(clippy::repeat_once)]
+
+trait Repeat {
+ fn repeat(&self) {}
+}
+
+impl Repeat for usize {
+ fn repeat(&self) {}
+}
+
+fn main() {
+ let _ = 42.repeat();
+}
-error: Calling `subsec_millis()` is more concise than this calculation
+error: calling `subsec_millis()` is more concise than this calculation
--> $DIR/duration_subsec.rs:10:24
|
LL | let bad_millis_1 = dur.subsec_micros() / 1_000;
|
= note: `-D clippy::duration-subsec` implied by `-D warnings`
-error: Calling `subsec_millis()` is more concise than this calculation
+error: calling `subsec_millis()` is more concise than this calculation
--> $DIR/duration_subsec.rs:11:24
|
LL | let bad_millis_2 = dur.subsec_nanos() / 1_000_000;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_millis()`
-error: Calling `subsec_micros()` is more concise than this calculation
+error: calling `subsec_micros()` is more concise than this calculation
--> $DIR/duration_subsec.rs:16:22
|
LL | let bad_micros = dur.subsec_nanos() / 1_000;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()`
-error: Calling `subsec_micros()` is more concise than this calculation
+error: calling `subsec_micros()` is more concise than this calculation
--> $DIR/duration_subsec.rs:21:13
|
LL | let _ = (&dur).subsec_nanos() / 1_000;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&dur).subsec_micros()`
-error: Calling `subsec_micros()` is more concise than this calculation
+error: calling `subsec_micros()` is more concise than this calculation
--> $DIR/duration_subsec.rs:25:13
|
LL | let _ = dur.subsec_nanos() / NANOS_IN_MICRO;
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:8:5
|
LL | X = 0x1_0000_0000,
|
= note: `-D clippy::enum-clike-unportable-variant` implied by `-D warnings`
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:15:5
|
LL | X = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:18:5
|
LL | A = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:25:5
|
LL | Z = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:26:5
|
LL | A = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:28:5
|
LL | C = (i32::MIN as isize) - 1,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:34:5
|
LL | Z = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:35:5
|
LL | A = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
-error: Clike enum variant discriminant is not portable to 32-bit targets
+error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:40:5
|
LL | X = <usize as Trait>::Number,
-error: Variant name ends with the enum's name
+error: variant name ends with the enum's name
--> $DIR/enum_variants.rs:16:5
|
LL | cFoo,
|
= note: `-D clippy::enum-variant-names` implied by `-D warnings`
-error: Variant name starts with the enum's name
+error: variant name starts with the enum's name
--> $DIR/enum_variants.rs:27:5
|
LL | FoodGood,
| ^^^^^^^^
-error: Variant name starts with the enum's name
+error: variant name starts with the enum's name
--> $DIR/enum_variants.rs:28:5
|
LL | FoodMiddle,
| ^^^^^^^^^^
-error: Variant name starts with the enum's name
+error: variant name starts with the enum's name
--> $DIR/enum_variants.rs:29:5
|
LL | FoodBad,
| ^^^^^^^
-error: All variants have the same prefix: `Food`
+error: all variants have the same prefix: `Food`
--> $DIR/enum_variants.rs:26:1
|
LL | / enum Food {
|
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: All variants have the same prefix: `CallType`
+error: all variants have the same prefix: `CallType`
--> $DIR/enum_variants.rs:36:1
|
LL | / enum BadCallType {
|
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: All variants have the same prefix: `Constant`
+error: all variants have the same prefix: `Constant`
--> $DIR/enum_variants.rs:48:1
|
LL | / enum Consts {
|
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: All variants have the same prefix: `With`
+error: all variants have the same prefix: `With`
--> $DIR/enum_variants.rs:82:1
|
LL | / enum Seallll {
|
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: All variants have the same prefix: `Prefix`
+error: all variants have the same prefix: `Prefix`
--> $DIR/enum_variants.rs:88:1
|
LL | / enum NonCaps {
|
= help: remove the prefixes and use full paths to the variants instead of glob imports
-error: All variants have the same prefix: `With`
+error: all variants have the same prefix: `With`
--> $DIR/enum_variants.rs:94:1
|
LL | / pub enum PubSeall {
--- /dev/null
+#![warn(clippy::float_equality_without_abs)]
+
+pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ (a - b) < f32::EPSILON
+}
+
+pub fn main() {
+ // all errors
+ is_roughly_equal(1.0, 2.0);
+ let a = 0.05;
+ let b = 0.0500001;
+
+ let _ = (a - b) < f32::EPSILON;
+ let _ = a - b < f32::EPSILON;
+ let _ = a - b.abs() < f32::EPSILON;
+ let _ = (a as f64 - b as f64) < f64::EPSILON;
+ let _ = 1.0 - 2.0 < f32::EPSILON;
+
+ let _ = f32::EPSILON > (a - b);
+ let _ = f32::EPSILON > a - b;
+ let _ = f32::EPSILON > a - b.abs();
+ let _ = f64::EPSILON > (a as f64 - b as f64);
+ let _ = f32::EPSILON > 1.0 - 2.0;
+
+ // those are correct
+ let _ = (a - b).abs() < f32::EPSILON;
+ let _ = (a as f64 - b as f64).abs() < f64::EPSILON;
+
+ let _ = f32::EPSILON > (a - b).abs();
+ let _ = f64::EPSILON > (a as f64 - b as f64).abs();
+}
--- /dev/null
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:4:5
+ |
+LL | (a - b) < f32::EPSILON
+ | -------^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(a - b).abs()`
+ |
+ = note: `-D clippy::float-equality-without-abs` implied by `-D warnings`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:13:13
+ |
+LL | let _ = (a - b) < f32::EPSILON;
+ | -------^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(a - b).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:14:13
+ |
+LL | let _ = a - b < f32::EPSILON;
+ | -----^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(a - b).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:15:13
+ |
+LL | let _ = a - b.abs() < f32::EPSILON;
+ | -----------^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(a - b.abs()).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:16:13
+ |
+LL | let _ = (a as f64 - b as f64) < f64::EPSILON;
+ | ---------------------^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(a as f64 - b as f64).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:17:13
+ |
+LL | let _ = 1.0 - 2.0 < f32::EPSILON;
+ | ---------^^^^^^^^^^^^^^^
+ | |
+ | help: add `.abs()`: `(1.0 - 2.0).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:19:13
+ |
+LL | let _ = f32::EPSILON > (a - b);
+ | ^^^^^^^^^^^^^^^-------
+ | |
+ | help: add `.abs()`: `(a - b).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:20:13
+ |
+LL | let _ = f32::EPSILON > a - b;
+ | ^^^^^^^^^^^^^^^-----
+ | |
+ | help: add `.abs()`: `(a - b).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:21:13
+ |
+LL | let _ = f32::EPSILON > a - b.abs();
+ | ^^^^^^^^^^^^^^^-----------
+ | |
+ | help: add `.abs()`: `(a - b.abs()).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:22:13
+ |
+LL | let _ = f64::EPSILON > (a as f64 - b as f64);
+ | ^^^^^^^^^^^^^^^---------------------
+ | |
+ | help: add `.abs()`: `(a as f64 - b as f64).abs()`
+
+error: float equality check without `.abs()`
+ --> $DIR/float_equality_without_abs.rs:23:13
+ |
+LL | let _ = f32::EPSILON > 1.0 - 2.0;
+ | ^^^^^^^^^^^^^^^---------
+ | |
+ | help: add `.abs()`: `(1.0 - 2.0).abs()`
+
+error: aborting due to 11 previous errors
+
-error: Matching on `Some` with `ok()` is redundant
+error: matching on `Some` with `ok()` is redundant
--> $DIR/if_let_some_result.rs:6:5
|
LL | if let Some(y) = x.parse().ok() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::if-let-some-result` implied by `-D warnings`
-help: Consider matching on `Ok(y)` and removing the call to `ok` instead
+help: consider matching on `Ok(y)` and removing the call to `ok` instead
|
LL | if let Ok(y) = x.parse() {
| ^^^^^^^^^^^^^^^^^^^^^^^^
-error: Matching on `Some` with `ok()` is redundant
+error: matching on `Some` with `ok()` is redundant
--> $DIR/if_let_some_result.rs:24:9
|
LL | if let Some(y) = x . parse() . ok () {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
-help: Consider matching on `Ok(y)` and removing the call to `ok` instead
+help: consider matching on `Ok(y)` and removing the call to `ok` instead
|
LL | if let Ok(y) = x . parse() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: Unnecessary boolean `not` operation
+error: unnecessary boolean `not` operation
--> $DIR/if_not_else.rs:9:5
|
LL | / if !bla() {
= note: `-D clippy::if-not-else` implied by `-D warnings`
= help: remove the `!` and swap the blocks of the `if`/`else`
-error: Unnecessary `!=` operation
+error: unnecessary `!=` operation
--> $DIR/if_not_else.rs:14:5
|
LL | / if 4 != 5 {
-error: Multiple implementations of this structure
+error: multiple implementations of this structure
--> $DIR/impl.rs:10:1
|
LL | / impl MyStruct {
| |_^
|
= note: `-D clippy::multiple-inherent-impl` implied by `-D warnings`
-note: First implementation here
+note: first implementation here
--> $DIR/impl.rs:6:1
|
LL | / impl MyStruct {
LL | | }
| |_^
-error: Multiple implementations of this structure
+error: multiple implementations of this structure
--> $DIR/impl.rs:24:5
|
LL | / impl super::MyStruct {
LL | | }
| |_____^
|
-note: First implementation here
+note: first implementation here
--> $DIR/impl.rs:6:1
|
LL | / impl MyStruct {
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:13:5
|
LL | / if u_8 > 0 {
|
= note: `-D clippy::implicit-saturating-sub` implied by `-D warnings`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:20:13
|
LL | / if u_8 > 0 {
LL | | }
| |_____________^ help: try: `u_8 = u_8.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:34:5
|
LL | / if u_16 > 0 {
LL | | }
| |_____^ help: try: `u_16 = u_16.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:44:5
|
LL | / if u_32 != 0 {
LL | | }
| |_____^ help: try: `u_32 = u_32.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:65:5
|
LL | / if u_64 > 0 {
LL | | }
| |_____^ help: try: `u_64 = u_64.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:70:5
|
LL | / if 0 < u_64 {
LL | | }
| |_____^ help: try: `u_64 = u_64.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:75:5
|
LL | / if 0 != u_64 {
LL | | }
| |_____^ help: try: `u_64 = u_64.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:96:5
|
LL | / if u_usize > 0 {
LL | | }
| |_____^ help: try: `u_usize = u_usize.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:108:5
|
LL | / if i_8 > i8::MIN {
LL | | }
| |_____^ help: try: `i_8 = i_8.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:113:5
|
LL | / if i_8 > i8::MIN {
LL | | }
| |_____^ help: try: `i_8 = i_8.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:118:5
|
LL | / if i_8 != i8::MIN {
LL | | }
| |_____^ help: try: `i_8 = i_8.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:123:5
|
LL | / if i_8 != i8::MIN {
LL | | }
| |_____^ help: try: `i_8 = i_8.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:133:5
|
LL | / if i_16 > i16::MIN {
LL | | }
| |_____^ help: try: `i_16 = i_16.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:138:5
|
LL | / if i_16 > i16::MIN {
LL | | }
| |_____^ help: try: `i_16 = i_16.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:143:5
|
LL | / if i_16 != i16::MIN {
LL | | }
| |_____^ help: try: `i_16 = i_16.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:148:5
|
LL | / if i_16 != i16::MIN {
LL | | }
| |_____^ help: try: `i_16 = i_16.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:158:5
|
LL | / if i_32 > i32::MIN {
LL | | }
| |_____^ help: try: `i_32 = i_32.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:163:5
|
LL | / if i_32 > i32::MIN {
LL | | }
| |_____^ help: try: `i_32 = i_32.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:168:5
|
LL | / if i_32 != i32::MIN {
LL | | }
| |_____^ help: try: `i_32 = i_32.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:173:5
|
LL | / if i_32 != i32::MIN {
LL | | }
| |_____^ help: try: `i_32 = i_32.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:183:5
|
LL | / if i64::MIN < i_64 {
LL | | }
| |_____^ help: try: `i_64 = i_64.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:188:5
|
LL | / if i64::MIN != i_64 {
LL | | }
| |_____^ help: try: `i_64 = i_64.saturating_sub(1);`
-error: Implicitly performing saturating subtraction
+error: implicitly performing saturating subtraction
--> $DIR/implicit_saturating_sub.rs:193:5
|
LL | / if i64::MIN < i_64 {
-error: Unnecessary `>= y + 1` or `x - 1 >=`
+error: unnecessary `>= y + 1` or `x - 1 >=`
--> $DIR/int_plus_one.rs:9:13
|
LL | let _ = x >= y + 1;
|
= note: `-D clippy::int-plus-one` implied by `-D warnings`
-error: Unnecessary `>= y + 1` or `x - 1 >=`
+error: unnecessary `>= y + 1` or `x - 1 >=`
--> $DIR/int_plus_one.rs:10:13
|
LL | let _ = y + 1 <= x;
| ^^^^^^^^^^ help: change it to: `y < x`
-error: Unnecessary `>= y + 1` or `x - 1 >=`
+error: unnecessary `>= y + 1` or `x - 1 >=`
--> $DIR/int_plus_one.rs:12:13
|
LL | let _ = x - 1 >= y;
| ^^^^^^^^^^ help: change it to: `x > y`
-error: Unnecessary `>= y + 1` or `x - 1 >=`
+error: unnecessary `>= y + 1` or `x - 1 >=`
--> $DIR/int_plus_one.rs:13:13
|
LL | let _ = y <= x - 1;
-error: Using `.iter().next()` on an array
+error: using `.iter().next()` on an array
--> $DIR/iter_next_slice.rs:9:5
|
LL | s.iter().next();
|
= note: `-D clippy::iter-next-slice` implied by `-D warnings`
-error: Using `.iter().next()` on a Slice without end index.
+error: using `.iter().next()` on a Slice without end index
--> $DIR/iter_next_slice.rs:12:5
|
LL | s[2..].iter().next();
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)`
-error: Using `.iter().next()` on a Slice without end index.
+error: using `.iter().next()` on a Slice without end index
--> $DIR/iter_next_slice.rs:15:5
|
LL | v[5..].iter().next();
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)`
-error: Using `.iter().next()` on an array
+error: using `.iter().next()` on an array
--> $DIR/iter_next_slice.rs:18:5
|
LL | v.iter().next();
fn test_slice(b: &[u8]) {
if !b.is_empty() {}
}
-
-mod issue_3807 {
- // Avoid suggesting changes to ranges if the user did not enable `range_is_empty`.
- // See https://github.com/rust-lang/rust/issues/48111#issuecomment-445132965
- fn no_suggestion() {
- let _ = (0..42).len() == 0;
- }
-}
fn test_slice(b: &[u8]) {
if b.len() != 0 {}
}
-
-mod issue_3807 {
- // Avoid suggesting changes to ranges if the user did not enable `range_is_empty`.
- // See https://github.com/rust-lang/rust/issues/48111#issuecomment-445132965
- fn no_suggestion() {
- let _ = (0..42).len() == 0;
- }
-}
// run-rustfix
-#![feature(range_is_empty)]
#![warn(clippy::len_zero)]
#![allow(unused)]
+// Now that `Range(Inclusive)::is_empty` is stable (1.47), we can always suggest this
mod issue_3807 {
- // With the feature enabled, `is_empty` should be suggested
- fn suggestion_is_fine() {
+ fn suggestion_is_fine_range() {
let _ = (0..42).is_empty();
}
+
+ fn suggestion_is_fine_range_inclusive() {
+ let _ = (0_u8..=42).is_empty();
+ }
}
fn main() {}
// run-rustfix
-#![feature(range_is_empty)]
#![warn(clippy::len_zero)]
#![allow(unused)]
+// Now that `Range(Inclusive)::is_empty` is stable (1.47), we can always suggest this
mod issue_3807 {
- // With the feature enabled, `is_empty` should be suggested
- fn suggestion_is_fine() {
+ fn suggestion_is_fine_range() {
let _ = (0..42).len() == 0;
}
+
+ fn suggestion_is_fine_range_inclusive() {
+ let _ = (0_u8..=42).len() == 0;
+ }
}
fn main() {}
error: length comparison to zero
- --> $DIR/len_zero_ranges.rs:10:17
+ --> $DIR/len_zero_ranges.rs:9:17
|
LL | let _ = (0..42).len() == 0;
| ^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(0..42).is_empty()`
|
= note: `-D clippy::len-zero` implied by `-D warnings`
-error: aborting due to previous error
+error: length comparison to zero
+ --> $DIR/len_zero_ranges.rs:13:17
+ |
+LL | let _ = (0_u8..=42).len() == 0;
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(0_u8..=42).is_empty()`
+
+error: aborting due to 2 previous errors
}
}
+mod issue_5729 {
+ use std::sync::Arc;
+
+ trait Foo {}
+
+ trait FooStorage {
+ fn foo_cloned(&self) -> Arc<dyn Foo>;
+ }
+
+ struct FooStorageImpl<T: Foo> {
+ foo: Arc<T>,
+ }
+
+ impl<T: Foo + 'static> FooStorage for FooStorageImpl<T> {
+ fn foo_cloned(&self) -> Arc<dyn Foo> {
+ let clone = Arc::clone(&self.foo);
+ clone
+ }
+ }
+}
+
fn main() {}
LL | 5
|
-error: aborting due to 2 previous errors
+error: returning the result of a `let` binding from a block
+ --> $DIR/let_and_return.rs:154:13
+ |
+LL | let clone = Arc::clone(&self.foo);
+ | ---------------------------------- unnecessary `let` binding
+LL | clone
+ | ^^^^^
+ |
+help: return the expression directly
+ |
+LL |
+LL | Arc::clone(&self.foo) as _
+ |
+
+error: aborting due to 3 previous errors
-error: You are using an explicit closure for copying elements
+error: you are using an explicit closure for copying elements
--> $DIR/map_clone.rs:10:22
|
LL | let _: Vec<i8> = vec![5_i8; 6].iter().map(|x| *x).collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()`
|
= note: `-D clippy::map-clone` implied by `-D warnings`
-error: You are using an explicit closure for cloning elements
+error: you are using an explicit closure for cloning elements
--> $DIR/map_clone.rs:11:26
|
LL | let _: Vec<String> = vec![String::new()].iter().map(|x| x.clone()).collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()`
-error: You are using an explicit closure for copying elements
+error: you are using an explicit closure for copying elements
--> $DIR/map_clone.rs:12:23
|
LL | let _: Vec<u32> = vec![42, 43].iter().map(|&x| x).collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()`
-error: You are using an explicit closure for copying elements
+error: you are using an explicit closure for copying elements
--> $DIR/map_clone.rs:14:26
|
LL | let _: Option<u64> = Some(&16).map(|b| *b);
- | ^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `Some(&16).copied()`
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&16).copied()`
-error: You are using an explicit closure for copying elements
+error: you are using an explicit closure for copying elements
--> $DIR/map_clone.rs:15:25
|
LL | let _: Option<u8> = Some(&1).map(|x| x.clone());
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `Some(&1).copied()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&1).copied()`
-error: You are needlessly cloning iterator elements
+error: you are needlessly cloning iterator elements
--> $DIR/map_clone.rs:26:29
|
LL | let _ = std::env::args().map(|v| v.clone());
- | ^^^^^^^^^^^^^^^^^^^ help: Remove the `map` call
+ | ^^^^^^^^^^^^^^^^^^^ help: remove the `map` call
error: aborting due to 6 previous errors
clippy::non_ascii_literal,
clippy::new_without_default,
clippy::needless_pass_by_value,
+ clippy::needless_lifetimes,
clippy::print_stdout,
clippy::must_use_candidate,
clippy::use_self,
use option_helpers::IteratorFalsePositives;
-pub struct T;
-
-impl T {
- pub fn add(self, other: T) -> T {
- self
- }
-
- // no error, not public interface
- pub(crate) fn drop(&mut self) {}
-
- // no error, private function
- fn neg(self) -> Self {
- self
- }
-
- // no error, private function
- fn eq(&self, other: T) -> bool {
- true
- }
-
- // No error; self is a ref.
- fn sub(&self, other: T) -> &T {
- self
- }
-
- // No error; different number of arguments.
- fn div(self) -> T {
- self
- }
-
- // No error; wrong return type.
- fn rem(self, other: T) {}
-
- // Fine
- fn into_u32(self) -> u32 {
- 0
- }
-
- fn into_u16(&self) -> u16 {
- 0
- }
-
- fn to_something(self) -> u32 {
- 0
- }
-
- fn new(self) -> Self {
- unimplemented!();
- }
-}
-
-pub struct T1;
-
-impl T1 {
- // Shouldn't trigger lint as it is unsafe.
- pub unsafe fn add(self, rhs: T1) -> T1 {
- self
- }
-
- // Should not trigger lint since this is an async function.
- pub async fn next(&mut self) -> Option<T1> {
- None
- }
-}
-
struct Lt<'a> {
foo: &'a u32,
}
}
}
+struct T;
+
impl Mul<T> for T {
type Output = T;
// No error, obviously.
-error: defining a method called `add` on this type; consider implementing the `std::ops::Add` trait or choosing a less ambiguous name
- --> $DIR/methods.rs:39:5
- |
-LL | / pub fn add(self, other: T) -> T {
-LL | | self
-LL | | }
- | |_____^
- |
- = note: `-D clippy::should-implement-trait` implied by `-D warnings`
-
error: methods called `new` usually return `Self`
- --> $DIR/methods.rs:169:5
+ --> $DIR/methods.rs:105:5
|
LL | / fn new() -> i32 {
LL | | 0
= note: `-D clippy::new-ret-no-self` implied by `-D warnings`
error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead.
- --> $DIR/methods.rs:188:13
+ --> $DIR/methods.rs:126:13
|
LL | let _ = v.iter().filter(|&x| *x < 0).next();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: replace `filter(|&x| *x < 0).next()` with `find(|&x| *x < 0)`
error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead.
- --> $DIR/methods.rs:191:13
+ --> $DIR/methods.rs:129:13
|
LL | let _ = v.iter().filter(|&x| {
| _____________^
| |___________________________^
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:208:22
+ --> $DIR/methods.rs:146:22
|
LL | let _ = v.iter().find(|&x| *x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x < 0)`
= note: `-D clippy::search-is-some` implied by `-D warnings`
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:209:20
+ --> $DIR/methods.rs:147:20
|
LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| **y == x)`
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:210:20
+ --> $DIR/methods.rs:148:20
|
LL | let _ = (0..1).find(|x| *x == 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| x == 0)`
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:211:22
+ --> $DIR/methods.rs:149:22
|
LL | let _ = v.iter().find(|x| **x == 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x == 0)`
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:214:13
+ --> $DIR/methods.rs:152:13
|
LL | let _ = v.iter().find(|&x| {
| _____________^
| |______________________________^
error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:220:22
+ --> $DIR/methods.rs:158:22
|
LL | let _ = v.iter().position(|&x| x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`
error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:223:13
+ --> $DIR/methods.rs:161:13
|
LL | let _ = v.iter().position(|&x| {
| _____________^
| |______________________________^
error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:229:22
+ --> $DIR/methods.rs:167:22
|
LL | let _ = v.iter().rposition(|&x| x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`
error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
- --> $DIR/methods.rs:232:13
+ --> $DIR/methods.rs:170:13
|
LL | let _ = v.iter().rposition(|&x| {
| _____________^
LL | | ).is_some();
| |______________________________^
-error: aborting due to 13 previous errors
+error: aborting due to 12 previous errors
-error: The function/method `takes_an_immutable_reference` doesn't need a mutable reference
+error: the function `takes_an_immutable_reference` doesn't need a mutable reference
--> $DIR/mut_reference.rs:17:34
|
LL | takes_an_immutable_reference(&mut 42);
|
= note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings`
-error: The function/method `as_ptr` doesn't need a mutable reference
+error: the function `as_ptr` doesn't need a mutable reference
--> $DIR/mut_reference.rs:19:12
|
LL | as_ptr(&mut 42);
| ^^^^^^^
-error: The function/method `takes_an_immutable_reference` doesn't need a mutable reference
+error: the method `takes_an_immutable_reference` doesn't need a mutable reference
--> $DIR/mut_reference.rs:23:44
|
LL | my_struct.takes_an_immutable_reference(&mut 42);
-error: Consider using an `AtomicBool` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicBool` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:6:5
|
LL | Mutex::new(true);
|
= note: `-D clippy::mutex-atomic` implied by `-D warnings`
-error: Consider using an `AtomicUsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:7:5
|
LL | Mutex::new(5usize);
| ^^^^^^^^^^^^^^^^^^
-error: Consider using an `AtomicIsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:8:5
|
LL | Mutex::new(9isize);
| ^^^^^^^^^^^^^^^^^^
-error: Consider using an `AtomicPtr` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:10:5
|
LL | Mutex::new(&x as *const u32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: Consider using an `AtomicPtr` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:11:5
|
LL | Mutex::new(&mut x as *mut u32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: Consider using an `AtomicUsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:12:5
|
LL | Mutex::new(0u32);
|
= note: `-D clippy::mutex-integer` implied by `-D warnings`
-error: Consider using an `AtomicIsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`.
+error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>`
--> $DIR/mutex_atomic.rs:13:5
|
LL | Mutex::new(0i32);
/// }
/// ```
///
-/// This should, too.
+/// With an explicit return type it should lint too
+/// ```
+/// fn main() -> () {
+/// unimplemented!();
+/// }
+/// ```
///
+/// This should, too.
/// ```rust
/// fn main() {
/// unimplemented!();
/// ```
///
/// This one too.
-///
/// ```no_run
/// fn main() {
/// unimplemented!();
/// fn main(){}
/// ```
///
+/// This shouldn't lint either, because main is async:
+/// ```
+/// async fn main() {
+/// assert_eq!(42, ANSWER);
+/// }
+/// ```
+///
+/// Same here, because the return type is not the unit type:
+/// ```
+/// fn main() -> Result<()> {
+/// Ok(())
+/// }
+/// ```
+///
/// This shouldn't lint either, because there's a `static`:
/// ```
/// static ANSWER: i32 = 42;
/// }
/// ```
///
+/// This shouldn't lint either, because there's a `const`:
+/// ```
+/// fn main() {
+/// assert_eq!(42, ANSWER);
+/// }
+///
+/// const ANSWER: i32 = 42;
+/// ```
+///
/// Neither should this lint because of `extern crate`:
/// ```
/// #![feature(test)]
/// }
/// ```
///
-/// We should not lint ignored examples:
+/// Neither should this lint because it has an extern block:
+/// ```
+/// extern {}
+/// fn main() {
+/// unimplemented!();
+/// }
+/// ```
+///
+/// This should not lint because there is another function defined:
+/// ```
+/// fn fun() {}
+///
+/// fn main() {
+/// unimplemented!();
+/// }
+/// ```
///
+/// We should not lint inside raw strings ...
+/// ```
+/// let string = r#"
+/// fn main() {
+/// unimplemented!();
+/// }
+/// "#;
+/// ```
+///
+/// ... or comments
+/// ```
+/// // fn main() {
+/// // let _inception = 42;
+/// // }
+/// let _inception = 42;
+/// ```
+///
+/// We should not lint ignored examples:
/// ```rust,ignore
/// fn main() {
/// unimplemented!();
/// ```
///
/// Or even non-rust examples:
-///
/// ```text
/// fn main() {
/// is what starts the program
= note: `-D clippy::needless-doctest-main` implied by `-D warnings`
error: needless `fn main` in doctest
- --> $DIR/needless_doc_main.rs:15:4
+ --> $DIR/needless_doc_main.rs:14:4
+ |
+LL | /// fn main() -> () {
+ | ^^^^^^^^^^^^^^^^^^
+
+error: needless `fn main` in doctest
+ --> $DIR/needless_doc_main.rs:21:4
|
LL | /// fn main() {
| ^^^^^^^^^^^^
error: needless `fn main` in doctest
- --> $DIR/needless_doc_main.rs:23:4
+ --> $DIR/needless_doc_main.rs:28:4
|
LL | /// fn main() {
| ^^^^^^^^^^^^
-error: aborting due to 3 previous errors
+error: aborting due to 4 previous errors
}
}
+fn read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ String::from("test")
+ } else {
+ String::new()
+ }
+}
+
fn main() {
let _ = test_end_of_fn();
let _ = test_no_semicolon();
}
}
+fn read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ return String::from("test");
+ } else {
+ return String::new();
+ }
+}
+
fn main() {
let _ = test_end_of_fn();
let _ = test_no_semicolon();
LL | _ => return,
| ^^^^^^ help: replace `return` with an empty block: `{}`
-error: aborting due to 12 previous errors
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:83:9
+ |
+LL | return String::from("test");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:85:9
+ |
+LL | return String::new();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()`
+
+error: aborting due to 14 previous errors
}
}
-struct MutPointerReturnerOk2;
+struct ConstPointerReturnerOk2;
-impl MutPointerReturnerOk2 {
+impl ConstPointerReturnerOk2 {
// should not trigger lint
pub fn new() -> *const Self {
unimplemented!();
unimplemented!();
}
}
+
+mod issue5435 {
+ struct V;
+
+ pub trait TraitRetSelf {
+ // should not trigger lint
+ fn new() -> Self;
+ }
+
+ pub trait TraitRet {
+ // should trigger lint as we are in trait definition
+ fn new() -> String;
+ }
+ pub struct StructRet;
+ impl TraitRet for StructRet {
+ // should not trigger lint as we are in the impl block
+ fn new() -> String {
+ unimplemented!();
+ }
+ }
+
+ pub trait TraitRet2 {
+ // should trigger lint
+ fn new(_: String) -> String;
+ }
+
+ trait TupleReturnerOk {
+ // should not trigger lint
+ fn new() -> (Self, u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerOk2 {
+ // should not trigger lint (it doesn't matter which element in the tuple is Self)
+ fn new() -> (u32, Self)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerOk3 {
+ // should not trigger lint (tuple can contain multiple Self)
+ fn new() -> (Self, Self)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait TupleReturnerBad {
+ // should trigger lint
+ fn new() -> (u32, u32) {
+ unimplemented!();
+ }
+ }
+
+ trait MutPointerReturnerOk {
+ // should not trigger lint
+ fn new() -> *mut Self
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait ConstPointerReturnerOk2 {
+ // should not trigger lint
+ fn new() -> *const Self
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait MutPointerReturnerBad {
+ // should trigger lint
+ fn new() -> *mut V {
+ unimplemented!();
+ }
+ }
+
+ trait GenericReturnerOk {
+ // should not trigger lint
+ fn new() -> Option<Self>
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk {
+ // should not trigger lint
+ fn new() -> (Option<Self>, u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk2 {
+ // should not trigger lint
+ fn new() -> ((Self, u32), u32)
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+
+ trait NestedReturnerOk3 {
+ // should not trigger lint
+ fn new() -> Option<(Self, u32)>
+ where
+ Self: Sized,
+ {
+ unimplemented!();
+ }
+ }
+}
LL | | }
| |_____^
-error: aborting due to 6 previous errors
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:224:9
+ |
+LL | fn new() -> String;
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:236:9
+ |
+LL | fn new(_: String) -> String;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:271:9
+ |
+LL | / fn new() -> (u32, u32) {
+LL | | unimplemented!();
+LL | | }
+ | |_________^
+
+error: methods called `new` usually return `Self`
+ --> $DIR/new_ret_no_self.rs:298:9
+ |
+LL | / fn new() -> *mut V {
+LL | | unimplemented!();
+LL | | }
+ | |_________^
+
+error: aborting due to 10 previous errors
let _ = opt.as_deref();
let _ = opt.as_deref_mut();
+
+ // Issue #5927
+ let _ = opt.as_deref();
}
let _ = opt.as_ref().map(|x| &**x);
let _ = opt.as_mut().map(|x| &mut **x);
+
+ // Issue #5927
+ let _ = opt.as_ref().map(std::ops::Deref::deref);
}
LL | let _ = opt.as_mut().map(|x| &mut **x);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()`
-error: aborting due to 16 previous errors
+error: called `.as_ref().map(std::ops::Deref::deref)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead
+ --> $DIR/option_as_ref_deref.rs:46:13
+ |
+LL | let _ = opt.as_ref().map(std::ops::Deref::deref);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()`
+
+error: aborting due to 17 previous errors
let _ = -1f64.to_degrees();
let _ = -1f64.to_radians();
+ // Chains containing any non-odd function should trigger (issue #5924)
+ let _ = -(1.0_f64.cos().cos());
+ let _ = -(1.0_f64.cos().sin());
+ let _ = -(1.0_f64.sin().cos());
+
+ // Chains of odd functions shouldn't trigger
+ let _ = -1f64.sin().sin();
+
let b = 3;
trip!(b * 8);
}
let _ = -1f64.to_degrees();
let _ = -1f64.to_radians();
+ // Chains containing any non-odd function should trigger (issue #5924)
+ let _ = -1.0_f64.cos().cos();
+ let _ = -1.0_f64.cos().sin();
+ let _ = -1.0_f64.sin().cos();
+
+ // Chains of odd functions shouldn't trigger
+ let _ = -1f64.sin().sin();
+
let b = 3;
trip!(b * 8);
}
LL | -1f32.abs();
| ^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1f32.abs())`
-error: aborting due to 9 previous errors
+error: unary minus has lower precedence than method call
+ --> $DIR/precedence.rs:52:13
+ |
+LL | let _ = -1.0_f64.cos().cos();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.cos().cos())`
+
+error: unary minus has lower precedence than method call
+ --> $DIR/precedence.rs:53:13
+ |
+LL | let _ = -1.0_f64.cos().sin();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.cos().sin())`
+
+error: unary minus has lower precedence than method call
+ --> $DIR/precedence.rs:54:13
+ |
+LL | let _ = -1.0_f64.sin().cos();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1.0_f64.sin().cos())`
+
+error: aborting due to 12 previous errors
// Rc<Box<T>>
-pub fn test6(a: Box<bool>) {}
+pub fn test6(a: Rc<bool>) {}
// Box<&T>
--> $DIR/redundant_allocation.rs:36:17
|
LL | pub fn test6(a: Rc<Box<bool>>) {}
- | ^^^^^^^^^^^^^ help: try: `Box<bool>`
+ | ^^^^^^^^^^^^^ help: try: `Rc<bool>`
error: usage of `Box<&T>`
--> $DIR/redundant_allocation.rs:40:22
-error: try not to call a closure in the expression where it is declared.
+error: try not to call a closure in the expression where it is declared
--> $DIR/redundant_closure_call_early.rs:9:17
|
LL | let mut k = (|m| m + 1)(i);
|
= note: `-D clippy::redundant-closure-call` implied by `-D warnings`
-error: try not to call a closure in the expression where it is declared.
+error: try not to call a closure in the expression where it is declared
--> $DIR/redundant_closure_call_early.rs:12:9
|
LL | k = (|a, b| a * b)(1, 5);
-error: try not to call a closure in the expression where it is declared.
+error: try not to call a closure in the expression where it is declared
--> $DIR/redundant_closure_call_fixable.rs:7:13
|
LL | let a = (|| 42)();
let shadowed_closure = || 2;
i = shadowed_closure();
i = shadowed_closure();
+
+ // Fix FP in #5916
+ let mut x;
+ let create = || 2 * 2;
+ x = create();
+ fun(move || {
+ x = create();
+ })
+}
+
+fn fun<T: 'static + FnMut()>(mut f: T) {
+ f();
}
for a in vec_a {
vec12.push(2u8.pow(a.kind));
}
+
+ // Fix #5902
+ let mut vec13: Vec<u8> = Vec::new();
+ let mut item = 0;
+ for _ in 0..10 {
+ vec13.push(item);
+ item += 10;
+ }
}
--- /dev/null
+#![warn(clippy::self_assignment)]
+
+pub struct S<'a> {
+ a: i32,
+ b: [i32; 10],
+ c: Vec<Vec<i32>>,
+ e: &'a mut i32,
+ f: &'a mut i32,
+}
+
+pub fn positives(mut a: usize, b: &mut u32, mut s: S) {
+ a = a;
+ *b = *b;
+ s = s;
+ s.a = s.a;
+ s.b[10] = s.b[5 + 5];
+ s.c[0][1] = s.c[0][1];
+ s.b[a] = s.b[a];
+ *s.e = *s.e;
+ s.b[a + 10] = s.b[10 + a];
+
+ let mut t = (0, 1);
+ t.1 = t.1;
+ t.0 = (t.0);
+}
+
+pub fn negatives_not_equal(mut a: usize, b: &mut usize, mut s: S) {
+ dbg!(&a);
+ a = *b;
+ dbg!(&a);
+ s.b[1] += s.b[1];
+ s.b[1] = s.b[2];
+ s.c[1][0] = s.c[0][1];
+ s.b[a] = s.b[*b];
+ s.b[a + 10] = s.b[a + 11];
+ *s.e = *s.f;
+
+ let mut t = (0, 1);
+ t.0 = t.1;
+}
+
+#[allow(clippy::eval_order_dependence)]
+pub fn negatives_side_effects() {
+ let mut v = vec![1, 2, 3, 4, 5];
+ let mut i = 0;
+ v[{
+ i += 1;
+ i
+ }] = v[{
+ i += 1;
+ i
+ }];
+
+ fn next(n: &mut usize) -> usize {
+ let v = *n;
+ *n += 1;
+ v
+ }
+
+ let mut w = vec![1, 2, 3, 4, 5];
+ let mut i = 0;
+ let i = &mut i;
+ w[next(i)] = w[next(i)];
+ w[next(i)] = w[next(i)];
+}
+
+fn main() {}
--- /dev/null
+error: self-assignment of `a` to `a`
+ --> $DIR/self_assignment.rs:12:5
+ |
+LL | a = a;
+ | ^^^^^
+ |
+ = note: `-D clippy::self-assignment` implied by `-D warnings`
+
+error: self-assignment of `*b` to `*b`
+ --> $DIR/self_assignment.rs:13:5
+ |
+LL | *b = *b;
+ | ^^^^^^^
+
+error: self-assignment of `s` to `s`
+ --> $DIR/self_assignment.rs:14:5
+ |
+LL | s = s;
+ | ^^^^^
+
+error: self-assignment of `s.a` to `s.a`
+ --> $DIR/self_assignment.rs:15:5
+ |
+LL | s.a = s.a;
+ | ^^^^^^^^^
+
+error: self-assignment of `s.b[5 + 5]` to `s.b[10]`
+ --> $DIR/self_assignment.rs:16:5
+ |
+LL | s.b[10] = s.b[5 + 5];
+ | ^^^^^^^^^^^^^^^^^^^^
+
+error: self-assignment of `s.c[0][1]` to `s.c[0][1]`
+ --> $DIR/self_assignment.rs:17:5
+ |
+LL | s.c[0][1] = s.c[0][1];
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: self-assignment of `s.b[a]` to `s.b[a]`
+ --> $DIR/self_assignment.rs:18:5
+ |
+LL | s.b[a] = s.b[a];
+ | ^^^^^^^^^^^^^^^
+
+error: self-assignment of `*s.e` to `*s.e`
+ --> $DIR/self_assignment.rs:19:5
+ |
+LL | *s.e = *s.e;
+ | ^^^^^^^^^^^
+
+error: self-assignment of `s.b[10 + a]` to `s.b[a + 10]`
+ --> $DIR/self_assignment.rs:20:5
+ |
+LL | s.b[a + 10] = s.b[10 + a];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: self-assignment of `t.1` to `t.1`
+ --> $DIR/self_assignment.rs:23:5
+ |
+LL | t.1 = t.1;
+ | ^^^^^^^^^
+
+error: self-assignment of `(t.0)` to `t.0`
+ --> $DIR/self_assignment.rs:24:5
+ |
+LL | t.0 = (t.0);
+ | ^^^^^^^^^^^
+
+error: aborting due to 11 previous errors
+
--- /dev/null
+// edition:2018
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(
+ clippy::missing_errors_doc,
+ clippy::needless_pass_by_value,
+ clippy::must_use_candidate,
+ clippy::unused_self,
+ clippy::needless_lifetimes,
+ clippy::missing_safety_doc,
+ clippy::wrong_self_convention
+)]
+
+use std::ops::Mul;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+fn main() {}
+
+pub struct T1;
+impl T1 {
+ // corner cases: should not lint
+
+ // no error, not public interface
+ pub(crate) fn drop(&mut self) {}
+
+ // no error, private function
+ fn neg(self) -> Self {
+ self
+ }
+
+ // no error, private function
+ fn eq(&self, other: Self) -> bool {
+ true
+ }
+
+ // No error; self is a ref.
+ fn sub(&self, other: Self) -> &Self {
+ self
+ }
+
+ // No error; different number of arguments.
+ fn div(self) -> Self {
+ self
+ }
+
+ // No error; wrong return type.
+ fn rem(self, other: Self) {}
+
+ // Fine
+ fn into_u32(self) -> u32 {
+ 0
+ }
+
+ fn into_u16(&self) -> u16 {
+ 0
+ }
+
+ fn to_something(self) -> u32 {
+ 0
+ }
+
+ fn new(self) -> Self {
+ unimplemented!();
+ }
+
+ pub fn next<'b>(&'b mut self) -> Option<&'b mut T1> {
+ unimplemented!();
+ }
+}
+
+pub struct T2;
+impl T2 {
+ // Shouldn't trigger lint as it is unsafe.
+ pub unsafe fn add(self, rhs: Self) -> Self {
+ self
+ }
+
+ // Should not trigger lint since this is an async function.
+ pub async fn next(&mut self) -> Option<Self> {
+ None
+ }
+}
--- /dev/null
+// edition:2018
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(
+ clippy::missing_errors_doc,
+ clippy::needless_pass_by_value,
+ clippy::must_use_candidate,
+ clippy::unused_self,
+ clippy::needless_lifetimes,
+ clippy::missing_safety_doc,
+ clippy::wrong_self_convention
+)]
+
+use std::ops::Mul;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+fn main() {}
+pub struct T;
+
+impl T {
+ // *****************************************
+ // trait method list part 1, should lint all
+ // *****************************************
+ pub fn add(self, other: T) -> T {
+ unimplemented!()
+ }
+
+ pub fn as_mut(&mut self) -> &mut T {
+ unimplemented!()
+ }
+
+ pub fn as_ref(&self) -> &T {
+ unimplemented!()
+ }
+
+ pub fn bitand(self, rhs: T) -> T {
+ unimplemented!()
+ }
+
+ pub fn bitor(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn bitxor(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn borrow(&self) -> &str {
+ unimplemented!()
+ }
+
+ pub fn borrow_mut(&mut self) -> &mut str {
+ unimplemented!()
+ }
+
+ pub fn clone(&self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn cmp(&self, other: &Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn default() -> Self {
+ unimplemented!()
+ }
+
+ pub fn deref(&self) -> &Self {
+ unimplemented!()
+ }
+
+ pub fn deref_mut(&mut self) -> &mut Self {
+ unimplemented!()
+ }
+
+ pub fn div(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn drop(&mut self) {
+ unimplemented!()
+ }
+ // **********
+ // part 1 end
+ // **********
+}
--- /dev/null
+error: method `add` can be confused for the standard trait method `std::ops::Add::add`
+ --> $DIR/method_list_1.rs:25:5
+ |
+LL | / pub fn add(self, other: T) -> T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::should-implement-trait` implied by `-D warnings`
+ = help: consider implementing the trait `std::ops::Add` or choosing a less ambiguous method name
+
+error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut`
+ --> $DIR/method_list_1.rs:29:5
+ |
+LL | / pub fn as_mut(&mut self) -> &mut T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name
+
+error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref`
+ --> $DIR/method_list_1.rs:33:5
+ |
+LL | / pub fn as_ref(&self) -> &T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name
+
+error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand`
+ --> $DIR/method_list_1.rs:37:5
+ |
+LL | / pub fn bitand(self, rhs: T) -> T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name
+
+error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor`
+ --> $DIR/method_list_1.rs:41:5
+ |
+LL | / pub fn bitor(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name
+
+error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor`
+ --> $DIR/method_list_1.rs:45:5
+ |
+LL | / pub fn bitxor(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name
+
+error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow`
+ --> $DIR/method_list_1.rs:49:5
+ |
+LL | / pub fn borrow(&self) -> &str {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name
+
+error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut`
+ --> $DIR/method_list_1.rs:53:5
+ |
+LL | / pub fn borrow_mut(&mut self) -> &mut str {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name
+
+error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone`
+ --> $DIR/method_list_1.rs:57:5
+ |
+LL | / pub fn clone(&self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name
+
+error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp`
+ --> $DIR/method_list_1.rs:61:5
+ |
+LL | / pub fn cmp(&self, other: &Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name
+
+error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref`
+ --> $DIR/method_list_1.rs:69:5
+ |
+LL | / pub fn deref(&self) -> &Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name
+
+error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut`
+ --> $DIR/method_list_1.rs:73:5
+ |
+LL | / pub fn deref_mut(&mut self) -> &mut Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name
+
+error: method `div` can be confused for the standard trait method `std::ops::Div::div`
+ --> $DIR/method_list_1.rs:77:5
+ |
+LL | / pub fn div(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name
+
+error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop`
+ --> $DIR/method_list_1.rs:81:5
+ |
+LL | / pub fn drop(&mut self) {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Drop` or choosing a less ambiguous method name
+
+error: aborting due to 14 previous errors
+
--- /dev/null
+// edition:2018
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(
+ clippy::missing_errors_doc,
+ clippy::needless_pass_by_value,
+ clippy::must_use_candidate,
+ clippy::unused_self,
+ clippy::needless_lifetimes,
+ clippy::missing_safety_doc,
+ clippy::wrong_self_convention
+)]
+
+use std::ops::Mul;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+fn main() {}
+pub struct T;
+
+impl T {
+ // *****************************************
+ // trait method list part 2, should lint all
+ // *****************************************
+
+ pub fn eq(&self, other: &Self) -> bool {
+ unimplemented!()
+ }
+
+ pub fn from_iter<T>(iter: T) -> Self {
+ unimplemented!()
+ }
+
+ pub fn from_str(s: &str) -> Result<Self, Self> {
+ unimplemented!()
+ }
+
+ pub fn hash(&self, state: &mut T) {
+ unimplemented!()
+ }
+
+ pub fn index(&self, index: usize) -> &Self {
+ unimplemented!()
+ }
+
+ pub fn index_mut(&mut self, index: usize) -> &mut Self {
+ unimplemented!()
+ }
+
+ pub fn into_iter(self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn mul(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn neg(self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn next(&mut self) -> Option<Self> {
+ unimplemented!()
+ }
+
+ pub fn not(self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn rem(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn shl(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn shr(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+
+ pub fn sub(self, rhs: Self) -> Self {
+ unimplemented!()
+ }
+ // **********
+ // part 2 end
+ // **********
+}
--- /dev/null
+error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq`
+ --> $DIR/method_list_2.rs:26:5
+ |
+LL | / pub fn eq(&self, other: &Self) -> bool {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::should-implement-trait` implied by `-D warnings`
+ = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name
+
+error: method `from_iter` can be confused for the standard trait method `std::iter::FromIterator::from_iter`
+ --> $DIR/method_list_2.rs:30:5
+ |
+LL | / pub fn from_iter<T>(iter: T) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::iter::FromIterator` or choosing a less ambiguous method name
+
+error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str`
+ --> $DIR/method_list_2.rs:34:5
+ |
+LL | / pub fn from_str(s: &str) -> Result<Self, Self> {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name
+
+error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash`
+ --> $DIR/method_list_2.rs:38:5
+ |
+LL | / pub fn hash(&self, state: &mut T) {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name
+
+error: method `index` can be confused for the standard trait method `std::ops::Index::index`
+ --> $DIR/method_list_2.rs:42:5
+ |
+LL | / pub fn index(&self, index: usize) -> &Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name
+
+error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut`
+ --> $DIR/method_list_2.rs:46:5
+ |
+LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name
+
+error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter`
+ --> $DIR/method_list_2.rs:50:5
+ |
+LL | / pub fn into_iter(self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name
+
+error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul`
+ --> $DIR/method_list_2.rs:54:5
+ |
+LL | / pub fn mul(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name
+
+error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg`
+ --> $DIR/method_list_2.rs:58:5
+ |
+LL | / pub fn neg(self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name
+
+error: method `next` can be confused for the standard trait method `std::iter::Iterator::next`
+ --> $DIR/method_list_2.rs:62:5
+ |
+LL | / pub fn next(&mut self) -> Option<Self> {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name
+
+error: method `not` can be confused for the standard trait method `std::ops::Not::not`
+ --> $DIR/method_list_2.rs:66:5
+ |
+LL | / pub fn not(self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name
+
+error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem`
+ --> $DIR/method_list_2.rs:70:5
+ |
+LL | / pub fn rem(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name
+
+error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl`
+ --> $DIR/method_list_2.rs:74:5
+ |
+LL | / pub fn shl(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name
+
+error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr`
+ --> $DIR/method_list_2.rs:78:5
+ |
+LL | / pub fn shr(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name
+
+error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub`
+ --> $DIR/method_list_2.rs:82:5
+ |
+LL | / pub fn sub(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name
+
+error: aborting due to 15 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::single_char_push_str)]
+
+fn main() {
+ let mut string = String::new();
+ string.push('R');
+ string.push('\'');
+
+ string.push('u');
+ string.push_str("st");
+ string.push_str("");
+ string.push('\x52');
+ string.push('\u{0052}');
+ string.push('a');
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::single_char_push_str)]
+
+fn main() {
+ let mut string = String::new();
+ string.push_str("R");
+ string.push_str("'");
+
+ string.push('u');
+ string.push_str("st");
+ string.push_str("");
+ string.push_str("\x52");
+ string.push_str("\u{0052}");
+ string.push_str(r##"a"##);
+}
--- /dev/null
+error: calling `push_str()` using a single-character string literal
+ --> $DIR/single_char_push_str.rs:6:5
+ |
+LL | string.push_str("R");
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('R')`
+ |
+ = note: `-D clippy::single-char-push-str` implied by `-D warnings`
+
+error: calling `push_str()` using a single-character string literal
+ --> $DIR/single_char_push_str.rs:7:5
+ |
+LL | string.push_str("'");
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/'')`
+
+error: calling `push_str()` using a single-character string literal
+ --> $DIR/single_char_push_str.rs:12:5
+ |
+LL | string.push_str("/x52");
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/x52')`
+
+error: calling `push_str()` using a single-character string literal
+ --> $DIR/single_char_push_str.rs:13:5
+ |
+LL | string.push_str("/u{0052}");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/u{0052}')`
+
+error: calling `push_str()` using a single-character string literal
+ --> $DIR/single_char_push_str.rs:14:5
+ |
+LL | string.push_str(r##"a"##);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('a')`
+
+error: aborting due to 5 previous errors
+
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `i32`
--> $DIR/stable_sort_primitive.rs:7:5
|
LL | vec.sort();
|
= note: `-D clippy::stable-sort-primitive` implied by `-D warnings`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `bool`
--> $DIR/stable_sort_primitive.rs:9:5
|
LL | vec.sort();
| ^^^^^^^^^^ help: try: `vec.sort_unstable()`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `char`
--> $DIR/stable_sort_primitive.rs:11:5
|
LL | vec.sort();
| ^^^^^^^^^^ help: try: `vec.sort_unstable()`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `str`
--> $DIR/stable_sort_primitive.rs:13:5
|
LL | vec.sort();
| ^^^^^^^^^^ help: try: `vec.sort_unstable()`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `tuple`
--> $DIR/stable_sort_primitive.rs:15:5
|
LL | vec.sort();
| ^^^^^^^^^^ help: try: `vec.sort_unstable()`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `array`
--> $DIR/stable_sort_primitive.rs:17:5
|
LL | vec.sort();
| ^^^^^^^^^^ help: try: `vec.sort_unstable()`
-error: Use sort_unstable instead of sort
+error: used sort instead of sort_unstable to sort primitive type `i32`
--> $DIR/stable_sort_primitive.rs:19:5
|
LL | arr.sort();
#![warn(clippy::suspicious_arithmetic_impl)]
-use std::ops::{Add, AddAssign, BitOrAssign, Div, DivAssign, Mul, MulAssign, Sub};
+use std::ops::{
+ Add, AddAssign, BitAnd, BitOr, BitOrAssign, BitXor, Div, DivAssign, Mul, MulAssign, Rem, Shl, Shr, Sub,
+};
#[derive(Copy, Clone)]
struct Foo(u32);
}
}
+impl Rem for Foo {
+ type Output = Foo;
+
+ fn rem(self, other: Self) -> Self {
+ Foo(self.0 / other.0)
+ }
+}
+
+impl BitAnd for Foo {
+ type Output = Foo;
+
+ fn bitand(self, other: Self) -> Self {
+ Foo(self.0 | other.0)
+ }
+}
+
+impl BitOr for Foo {
+ type Output = Foo;
+
+ fn bitor(self, other: Self) -> Self {
+ Foo(self.0 ^ other.0)
+ }
+}
+
+impl BitXor for Foo {
+ type Output = Foo;
+
+ fn bitxor(self, other: Self) -> Self {
+ Foo(self.0 & other.0)
+ }
+}
+
+impl Shl for Foo {
+ type Output = Foo;
+
+ fn shl(self, other: Self) -> Self {
+ Foo(self.0 >> other.0)
+ }
+}
+
+impl Shr for Foo {
+ type Output = Foo;
+
+ fn shr(self, other: Self) -> Self {
+ Foo(self.0 << other.0)
+ }
+}
+
struct Bar(i32);
impl Add for Bar {
error: suspicious use of binary operator in `Add` impl
- --> $DIR/suspicious_arithmetic_impl.rs:11:20
+ --> $DIR/suspicious_arithmetic_impl.rs:13:20
|
LL | Foo(self.0 - other.0)
| ^
= note: `-D clippy::suspicious-arithmetic-impl` implied by `-D warnings`
error: suspicious use of binary operator in `AddAssign` impl
- --> $DIR/suspicious_arithmetic_impl.rs:17:23
+ --> $DIR/suspicious_arithmetic_impl.rs:19:23
|
LL | *self = *self - other;
| ^
= note: `#[deny(clippy::suspicious_op_assign_impl)]` on by default
error: suspicious use of binary operator in `MulAssign` impl
- --> $DIR/suspicious_arithmetic_impl.rs:30:16
+ --> $DIR/suspicious_arithmetic_impl.rs:32:16
|
LL | self.0 /= other.0;
| ^^
-error: aborting due to 3 previous errors
+error: suspicious use of binary operator in `Rem` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:70:20
+ |
+LL | Foo(self.0 / other.0)
+ | ^
+
+error: suspicious use of binary operator in `BitAnd` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:78:20
+ |
+LL | Foo(self.0 | other.0)
+ | ^
+
+error: suspicious use of binary operator in `BitOr` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:86:20
+ |
+LL | Foo(self.0 ^ other.0)
+ | ^
+
+error: suspicious use of binary operator in `BitXor` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:94:20
+ |
+LL | Foo(self.0 & other.0)
+ | ^
+
+error: suspicious use of binary operator in `Shl` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:102:20
+ |
+LL | Foo(self.0 >> other.0)
+ | ^^
+
+error: suspicious use of binary operator in `Shr` impl
+ --> $DIR/suspicious_arithmetic_impl.rs:110:20
+ |
+LL | Foo(self.0 << other.0)
+ | ^^
+
+error: aborting due to 9 previous errors
--- /dev/null
+#![warn(clippy::to_string_in_display)]
+#![allow(clippy::inherent_to_string_shadow_display)]
+
+use std::fmt;
+
+struct A;
+impl A {
+ fn fmt(&self) {
+ self.to_string();
+ }
+}
+
+trait B {
+ fn fmt(&self) {}
+}
+
+impl B for A {
+ fn fmt(&self) {
+ self.to_string();
+ }
+}
+
+impl fmt::Display for A {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.to_string())
+ }
+}
+
+fn fmt(a: A) {
+ a.to_string();
+}
+
+struct C;
+
+impl C {
+ fn to_string(&self) -> String {
+ String::from("I am C")
+ }
+}
+
+impl fmt::Display for C {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.to_string())
+ }
+}
+
+enum D {
+ E(String),
+ F,
+}
+
+impl std::fmt::Display for D {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self {
+ Self::E(string) => write!(f, "E {}", string.to_string()),
+ Self::F => write!(f, "F"),
+ }
+ }
+}
+
+fn main() {
+ let a = A;
+ a.to_string();
+ a.fmt();
+ fmt(a);
+
+ let c = C;
+ c.to_string();
+}
--- /dev/null
+error: using `to_string` in `fmt::Display` implementation might lead to infinite recursion
+ --> $DIR/to_string_in_display.rs:25:25
+ |
+LL | write!(f, "{}", self.to_string())
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::to-string-in-display` implied by `-D warnings`
+
+error: aborting due to previous error
+
+#![feature(const_fn_transmute)]
#![allow(dead_code)]
extern crate core;
}
#[warn(clippy::transmute_int_to_float)]
-fn int_to_float() {
- let _: f32 = unsafe { std::mem::transmute(0_u32) };
- let _: f32 = unsafe { std::mem::transmute(0_i32) };
+mod int_to_float {
+ fn test() {
+ let _: f32 = unsafe { std::mem::transmute(0_u32) };
+ let _: f32 = unsafe { std::mem::transmute(0_i32) };
+ let _: f64 = unsafe { std::mem::transmute(0_u64) };
+ let _: f64 = unsafe { std::mem::transmute(0_i64) };
+ }
+
+ mod issue_5747 {
+ const VALUE32: f32 = unsafe { std::mem::transmute(0_u32) };
+ const VALUE64: f64 = unsafe { std::mem::transmute(0_i64) };
+
+ const fn from_bits_32(v: i32) -> f32 {
+ unsafe { std::mem::transmute(v) }
+ }
+
+ const fn from_bits_64(v: u64) -> f64 {
+ unsafe { std::mem::transmute(v) }
+ }
+ }
}
fn bytes_to_str(b: &[u8], mb: &mut [u8]) {
error: transmute from a type (`&T`) to itself
- --> $DIR/transmute.rs:19:20
+ --> $DIR/transmute.rs:20:20
|
LL | let _: &'a T = core::intrinsics::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::useless-transmute` implied by `-D warnings`
error: transmute from a reference to a pointer
- --> $DIR/transmute.rs:23:23
+ --> $DIR/transmute.rs:24:23
|
LL | let _: *const T = core::intrinsics::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T`
error: transmute from a reference to a pointer
- --> $DIR/transmute.rs:25:21
+ --> $DIR/transmute.rs:26:21
|
LL | let _: *mut T = core::intrinsics::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T`
error: transmute from a reference to a pointer
- --> $DIR/transmute.rs:27:23
+ --> $DIR/transmute.rs:28:23
|
LL | let _: *const U = core::intrinsics::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U`
error: transmute from a type (`std::vec::Vec<i32>`) to itself
- --> $DIR/transmute.rs:33:27
+ --> $DIR/transmute.rs:34:27
|
LL | let _: Vec<i32> = core::intrinsics::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`std::vec::Vec<i32>`) to itself
- --> $DIR/transmute.rs:35:27
+ --> $DIR/transmute.rs:36:27
|
LL | let _: Vec<i32> = core::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`std::vec::Vec<i32>`) to itself
- --> $DIR/transmute.rs:37:27
+ --> $DIR/transmute.rs:38:27
|
LL | let _: Vec<i32> = std::intrinsics::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`std::vec::Vec<i32>`) to itself
- --> $DIR/transmute.rs:39:27
+ --> $DIR/transmute.rs:40:27
|
LL | let _: Vec<i32> = std::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`std::vec::Vec<i32>`) to itself
- --> $DIR/transmute.rs:41:27
+ --> $DIR/transmute.rs:42:27
|
LL | let _: Vec<i32> = my_transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^
error: transmute from an integer to a pointer
- --> $DIR/transmute.rs:43:31
+ --> $DIR/transmute.rs:44:31
|
LL | let _: *const usize = std::mem::transmute(5_isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize`
error: transmute from an integer to a pointer
- --> $DIR/transmute.rs:47:31
+ --> $DIR/transmute.rs:48:31
|
LL | let _: *const usize = std::mem::transmute(1 + 1usize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize`
error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`)
- --> $DIR/transmute.rs:62:24
+ --> $DIR/transmute.rs:63:24
|
LL | let _: Usize = core::intrinsics::transmute(int_const_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::crosspointer-transmute` implied by `-D warnings`
error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`)
- --> $DIR/transmute.rs:64:24
+ --> $DIR/transmute.rs:65:24
|
LL | let _: Usize = core::intrinsics::transmute(int_mut_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`)
- --> $DIR/transmute.rs:66:31
+ --> $DIR/transmute.rs:67:31
|
LL | let _: *const Usize = core::intrinsics::transmute(my_int());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`)
- --> $DIR/transmute.rs:68:29
+ --> $DIR/transmute.rs:69:29
|
LL | let _: *mut Usize = core::intrinsics::transmute(my_int());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from a `u32` to a `char`
- --> $DIR/transmute.rs:74:28
+ --> $DIR/transmute.rs:75:28
|
LL | let _: char = unsafe { std::mem::transmute(0_u32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_u32).unwrap()`
= note: `-D clippy::transmute-int-to-char` implied by `-D warnings`
error: transmute from a `i32` to a `char`
- --> $DIR/transmute.rs:75:28
+ --> $DIR/transmute.rs:76:28
|
LL | let _: char = unsafe { std::mem::transmute(0_i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_i32 as u32).unwrap()`
error: transmute from a `u8` to a `bool`
- --> $DIR/transmute.rs:80:28
+ --> $DIR/transmute.rs:81:28
|
LL | let _: bool = unsafe { std::mem::transmute(0_u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0`
= note: `-D clippy::transmute-int-to-bool` implied by `-D warnings`
error: transmute from a `u32` to a `f32`
- --> $DIR/transmute.rs:85:27
+ --> $DIR/transmute.rs:87:31
|
-LL | let _: f32 = unsafe { std::mem::transmute(0_u32) };
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_u32)`
+LL | let _: f32 = unsafe { std::mem::transmute(0_u32) };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_u32)`
|
= note: `-D clippy::transmute-int-to-float` implied by `-D warnings`
error: transmute from a `i32` to a `f32`
- --> $DIR/transmute.rs:86:27
+ --> $DIR/transmute.rs:88:31
+ |
+LL | let _: f32 = unsafe { std::mem::transmute(0_i32) };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_i32 as u32)`
+
+error: transmute from a `u64` to a `f64`
+ --> $DIR/transmute.rs:89:31
+ |
+LL | let _: f64 = unsafe { std::mem::transmute(0_u64) };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f64::from_bits(0_u64)`
+
+error: transmute from a `i64` to a `f64`
+ --> $DIR/transmute.rs:90:31
|
-LL | let _: f32 = unsafe { std::mem::transmute(0_i32) };
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_i32 as u32)`
+LL | let _: f64 = unsafe { std::mem::transmute(0_i64) };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f64::from_bits(0_i64 as u64)`
error: transmute from a `&[u8]` to a `&str`
- --> $DIR/transmute.rs:90:28
+ --> $DIR/transmute.rs:108:28
|
LL | let _: &str = unsafe { std::mem::transmute(b) };
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(b).unwrap()`
= note: `-D clippy::transmute-bytes-to-str` implied by `-D warnings`
error: transmute from a `&mut [u8]` to a `&mut str`
- --> $DIR/transmute.rs:91:32
+ --> $DIR/transmute.rs:109:32
|
LL | let _: &mut str = unsafe { std::mem::transmute(mb) };
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()`
-error: aborting due to 22 previous errors
+error: aborting due to 24 previous errors
-#[warn(clippy::transmute_float_to_int)]
+#![feature(const_fn_transmute)]
+#![warn(clippy::transmute_float_to_int)]
fn float_to_int() {
let _: u32 = unsafe { std::mem::transmute(1f32) };
let _: u64 = unsafe { std::mem::transmute(-1.0) };
}
+mod issue_5747 {
+ const VALUE32: i32 = unsafe { std::mem::transmute(1f32) };
+ const VALUE64: u64 = unsafe { std::mem::transmute(1f64) };
+
+ const fn to_bits_32(v: f32) -> u32 {
+ unsafe { std::mem::transmute(v) }
+ }
+
+ const fn to_bits_64(v: f64) -> i64 {
+ unsafe { std::mem::transmute(v) }
+ }
+}
+
fn main() {}
error: transmute from a `f32` to a `u32`
- --> $DIR/transmute_float_to_int.rs:4:27
+ --> $DIR/transmute_float_to_int.rs:5:27
|
LL | let _: u32 = unsafe { std::mem::transmute(1f32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits()`
= note: `-D clippy::transmute-float-to-int` implied by `-D warnings`
error: transmute from a `f32` to a `i32`
- --> $DIR/transmute_float_to_int.rs:5:27
+ --> $DIR/transmute_float_to_int.rs:6:27
|
LL | let _: i32 = unsafe { std::mem::transmute(1f32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits() as i32`
error: transmute from a `f64` to a `u64`
- --> $DIR/transmute_float_to_int.rs:6:27
+ --> $DIR/transmute_float_to_int.rs:7:27
|
LL | let _: u64 = unsafe { std::mem::transmute(1f64) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits()`
error: transmute from a `f64` to a `i64`
- --> $DIR/transmute_float_to_int.rs:7:27
+ --> $DIR/transmute_float_to_int.rs:8:27
|
LL | let _: i64 = unsafe { std::mem::transmute(1f64) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits() as i64`
error: transmute from a `f64` to a `u64`
- --> $DIR/transmute_float_to_int.rs:8:27
+ --> $DIR/transmute_float_to_int.rs:9:27
|
LL | let _: u64 = unsafe { std::mem::transmute(1.0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.0f64.to_bits()`
error: transmute from a `f64` to a `u64`
- --> $DIR/transmute_float_to_int.rs:9:27
+ --> $DIR/transmute_float_to_int.rs:10:27
|
LL | let _: u64 = unsafe { std::mem::transmute(-1.0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-1.0f64).to_bits()`
pub fn c(d: &u16) {}
}
+mod issue5876 {
+ // Don't lint here as it is always inlined
+ #[inline(always)]
+ fn foo_always(x: &i32) {
+ println!("{}", x);
+ }
+
+ #[inline(never)]
+ fn foo_never(x: &i32) {
+ println!("{}", x);
+ }
+
+ #[inline]
+ fn foo(x: &i32) {
+ println!("{}", x);
+ }
+}
+
fn main() {
let (mut foo, bar) = (Foo(0), Bar([0; 24]));
let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0);
LL | fn trait_method2(&self, _color: &Color);
| ^^^^^^ help: consider passing by value instead: `Color`
-error: aborting due to 15 previous errors
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
+ --> $DIR/trivially_copy_pass_by_ref.rs:108:21
+ |
+LL | fn foo_never(x: &i32) {
+ | ^^^^ help: consider passing by value instead: `i32`
+
+error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
+ --> $DIR/trivially_copy_pass_by_ref.rs:113:15
+ |
+LL | fn foo(x: &i32) {
+ | ^^^^ help: consider passing by value instead: `i32`
+
+error: aborting due to 17 previous errors
let _ = &encoded.clone();
}
}
+
+mod issue2076 {
+ use std::rc::Rc;
+
+ macro_rules! try_opt {
+ ($expr: expr) => {
+ match $expr {
+ Some(value) => value,
+ None => return None,
+ }
+ };
+ }
+
+ fn func() -> Option<Rc<u8>> {
+ let rc = Rc::new(42);
+ Some(try_opt!(Some(rc)).clone())
+ }
+}
LL | let _ = &<&[u8]>::clone(encoded);
| ^^^^^^^^^^^^^^^^^^^^^^^
-error: aborting due to 11 previous errors
+error: using `.clone()` on a ref-counted pointer
+ --> $DIR/unnecessary_clone.rs:108:14
+ |
+LL | Some(try_opt!(Some(rc)).clone())
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `Rc::<u8>::clone(&try_opt!(Some(rc)))`
+
+error: aborting due to 12 previous errors
--- /dev/null
+// run-rustfix
+#![warn(clippy::unnecessary_lazy_evaluations)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::bind_instead_of_map)]
+
+struct Deep(Option<usize>);
+
+#[derive(Copy, Clone)]
+struct SomeStruct {
+ some_field: usize,
+}
+
+impl SomeStruct {
+ fn return_some_field(&self) -> usize {
+ self.some_field
+ }
+}
+
+fn some_call<T: Default>() -> T {
+ T::default()
+}
+
+fn main() {
+ let astronomers_pi = 10;
+ let ext_arr: [usize; 1] = [2];
+ let ext_str = SomeStruct { some_field: 10 };
+
+ let mut opt = Some(42);
+ let ext_opt = Some(42);
+ let nested_opt = Some(Some(42));
+ let nested_tuple_opt = Some(Some((42, 43)));
+
+ // Should lint - Option
+ let _ = opt.unwrap_or(2);
+ let _ = opt.unwrap_or(astronomers_pi);
+ let _ = opt.unwrap_or(ext_str.some_field);
+ let _ = opt.unwrap_or(ext_arr[0]);
+ let _ = opt.and(ext_opt);
+ let _ = opt.or(ext_opt);
+ let _ = opt.or(None);
+ let _ = opt.get_or_insert(2);
+ let _ = opt.ok_or(2);
+ let _ = opt.ok_or(ext_arr[0]);
+
+ // Cases when unwrap is not called on a simple variable
+ let _ = Some(10).unwrap_or(2);
+ let _ = Some(10).and(ext_opt);
+ let _: Option<usize> = None.or(ext_opt);
+ let _ = None.get_or_insert(2);
+ let _: Result<usize, usize> = None.ok_or(2);
+ let _: Option<usize> = None.or(None);
+
+ let mut deep = Deep(Some(42));
+ let _ = deep.0.unwrap_or(2);
+ let _ = deep.0.and(ext_opt);
+ let _ = deep.0.or(None);
+ let _ = deep.0.get_or_insert(2);
+ let _ = deep.0.ok_or(2);
+
+ // Should not lint - Option
+ let _ = opt.unwrap_or_else(|| ext_str.return_some_field());
+ let _ = nested_opt.unwrap_or_else(|| Some(some_call()));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call())));
+ let _ = opt.or_else(some_call);
+ let _ = opt.or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(some_call);
+ let _ = deep.0.get_or_insert_with(|| some_call());
+ let _ = deep.0.or_else(some_call);
+ let _ = deep.0.or_else(|| some_call());
+
+ // These are handled by bind_instead_of_map
+ let _ = Some(10).and_then(|idx| Some(ext_arr[idx]));
+ let _ = Some(10).and_then(|idx| Some(idx));
+ let _: Option<usize> = None.or_else(|| Some(3));
+ let _ = deep.0.or_else(|| Some(3));
+ let _ = opt.or_else(|| Some(3));
+
+ // Should lint - Result
+ let res: Result<usize, usize> = Err(5);
+ let res2: Result<usize, SomeStruct> = Err(SomeStruct { some_field: 5 });
+
+ let _ = res2.unwrap_or(2);
+ let _ = res2.unwrap_or(astronomers_pi);
+ let _ = res2.unwrap_or(ext_str.some_field);
+
+ // Should not lint - Result
+ let _ = res.unwrap_or_else(|err| err);
+ let _ = res.unwrap_or_else(|err| ext_arr[err]);
+ let _ = res2.unwrap_or_else(|err| err.some_field);
+ let _ = res2.unwrap_or_else(|err| err.return_some_field());
+ let _ = res2.unwrap_or_else(|_| ext_str.return_some_field());
+
+ let _: Result<usize, usize> = res.and_then(|x| Ok(x));
+ let _: Result<usize, usize> = res.and_then(|x| Err(x));
+
+ let _: Result<usize, usize> = res.or_else(|err| Ok(err));
+ let _: Result<usize, usize> = res.or_else(|err| Err(err));
+
+ // These are handled by bind_instead_of_map
+ let _: Result<usize, usize> = res.and_then(|_| Ok(2));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.and_then(|_| Err(2));
+ let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Ok(2));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Err(2));
+ let _: Result<usize, usize> = res.or_else(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Err(ext_str.some_field));
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::unnecessary_lazy_evaluations)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::bind_instead_of_map)]
+
+struct Deep(Option<usize>);
+
+#[derive(Copy, Clone)]
+struct SomeStruct {
+ some_field: usize,
+}
+
+impl SomeStruct {
+ fn return_some_field(&self) -> usize {
+ self.some_field
+ }
+}
+
+fn some_call<T: Default>() -> T {
+ T::default()
+}
+
+fn main() {
+ let astronomers_pi = 10;
+ let ext_arr: [usize; 1] = [2];
+ let ext_str = SomeStruct { some_field: 10 };
+
+ let mut opt = Some(42);
+ let ext_opt = Some(42);
+ let nested_opt = Some(Some(42));
+ let nested_tuple_opt = Some(Some((42, 43)));
+
+ // Should lint - Option
+ let _ = opt.unwrap_or_else(|| 2);
+ let _ = opt.unwrap_or_else(|| astronomers_pi);
+ let _ = opt.unwrap_or_else(|| ext_str.some_field);
+ let _ = opt.unwrap_or_else(|| ext_arr[0]);
+ let _ = opt.and_then(|_| ext_opt);
+ let _ = opt.or_else(|| ext_opt);
+ let _ = opt.or_else(|| None);
+ let _ = opt.get_or_insert_with(|| 2);
+ let _ = opt.ok_or_else(|| 2);
+ let _ = opt.ok_or_else(|| ext_arr[0]);
+
+ // Cases when unwrap is not called on a simple variable
+ let _ = Some(10).unwrap_or_else(|| 2);
+ let _ = Some(10).and_then(|_| ext_opt);
+ let _: Option<usize> = None.or_else(|| ext_opt);
+ let _ = None.get_or_insert_with(|| 2);
+ let _: Result<usize, usize> = None.ok_or_else(|| 2);
+ let _: Option<usize> = None.or_else(|| None);
+
+ let mut deep = Deep(Some(42));
+ let _ = deep.0.unwrap_or_else(|| 2);
+ let _ = deep.0.and_then(|_| ext_opt);
+ let _ = deep.0.or_else(|| None);
+ let _ = deep.0.get_or_insert_with(|| 2);
+ let _ = deep.0.ok_or_else(|| 2);
+
+ // Should not lint - Option
+ let _ = opt.unwrap_or_else(|| ext_str.return_some_field());
+ let _ = nested_opt.unwrap_or_else(|| Some(some_call()));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call())));
+ let _ = opt.or_else(some_call);
+ let _ = opt.or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(some_call);
+ let _ = deep.0.get_or_insert_with(|| some_call());
+ let _ = deep.0.or_else(some_call);
+ let _ = deep.0.or_else(|| some_call());
+
+ // These are handled by bind_instead_of_map
+ let _ = Some(10).and_then(|idx| Some(ext_arr[idx]));
+ let _ = Some(10).and_then(|idx| Some(idx));
+ let _: Option<usize> = None.or_else(|| Some(3));
+ let _ = deep.0.or_else(|| Some(3));
+ let _ = opt.or_else(|| Some(3));
+
+ // Should lint - Result
+ let res: Result<usize, usize> = Err(5);
+ let res2: Result<usize, SomeStruct> = Err(SomeStruct { some_field: 5 });
+
+ let _ = res2.unwrap_or_else(|_| 2);
+ let _ = res2.unwrap_or_else(|_| astronomers_pi);
+ let _ = res2.unwrap_or_else(|_| ext_str.some_field);
+
+ // Should not lint - Result
+ let _ = res.unwrap_or_else(|err| err);
+ let _ = res.unwrap_or_else(|err| ext_arr[err]);
+ let _ = res2.unwrap_or_else(|err| err.some_field);
+ let _ = res2.unwrap_or_else(|err| err.return_some_field());
+ let _ = res2.unwrap_or_else(|_| ext_str.return_some_field());
+
+ let _: Result<usize, usize> = res.and_then(|x| Ok(x));
+ let _: Result<usize, usize> = res.and_then(|x| Err(x));
+
+ let _: Result<usize, usize> = res.or_else(|err| Ok(err));
+ let _: Result<usize, usize> = res.or_else(|err| Err(err));
+
+ // These are handled by bind_instead_of_map
+ let _: Result<usize, usize> = res.and_then(|_| Ok(2));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.and_then(|_| Err(2));
+ let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Ok(2));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Err(2));
+ let _: Result<usize, usize> = res.or_else(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Err(ext_str.some_field));
+}
--- /dev/null
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:34:13
+ |
+LL | let _ = opt.unwrap_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `opt.unwrap_or(2)`
+ |
+ = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:35:13
+ |
+LL | let _ = opt.unwrap_or_else(|| astronomers_pi);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:36:13
+ |
+LL | let _ = opt.unwrap_or_else(|| ext_str.some_field);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:37:13
+ |
+LL | let _ = opt.unwrap_or_else(|| ext_arr[0]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `opt.unwrap_or(ext_arr[0])`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:38:13
+ |
+LL | let _ = opt.and_then(|_| ext_opt);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `and` instead: `opt.and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:39:13
+ |
+LL | let _ = opt.or_else(|| ext_opt);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: Use `or` instead: `opt.or(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:40:13
+ |
+LL | let _ = opt.or_else(|| None);
+ | ^^^^^^^^^^^^^^^^^^^^ help: Use `or` instead: `opt.or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:41:13
+ |
+LL | let _ = opt.get_or_insert_with(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `get_or_insert` instead: `opt.get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:42:13
+ |
+LL | let _ = opt.ok_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^ help: Use `ok_or` instead: `opt.ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:43:13
+ |
+LL | let _ = opt.ok_or_else(|| ext_arr[0]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `ok_or` instead: `opt.ok_or(ext_arr[0])`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:46:13
+ |
+LL | let _ = Some(10).unwrap_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `Some(10).unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:47:13
+ |
+LL | let _ = Some(10).and_then(|_| ext_opt);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `and` instead: `Some(10).and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:48:28
+ |
+LL | let _: Option<usize> = None.or_else(|| ext_opt);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `or` instead: `None.or(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:49:13
+ |
+LL | let _ = None.get_or_insert_with(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `get_or_insert` instead: `None.get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:50:35
+ |
+LL | let _: Result<usize, usize> = None.ok_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^ help: Use `ok_or` instead: `None.ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:51:28
+ |
+LL | let _: Option<usize> = None.or_else(|| None);
+ | ^^^^^^^^^^^^^^^^^^^^^ help: Use `or` instead: `None.or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:54:13
+ |
+LL | let _ = deep.0.unwrap_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `deep.0.unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:55:13
+ |
+LL | let _ = deep.0.and_then(|_| ext_opt);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `and` instead: `deep.0.and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:56:13
+ |
+LL | let _ = deep.0.or_else(|| None);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: Use `or` instead: `deep.0.or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:57:13
+ |
+LL | let _ = deep.0.get_or_insert_with(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `get_or_insert` instead: `deep.0.get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
+ --> $DIR/unnecessary_lazy_eval.rs:58:13
+ |
+LL | let _ = deep.0.ok_or_else(|| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: Use `ok_or` instead: `deep.0.ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
+ --> $DIR/unnecessary_lazy_eval.rs:84:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| 2);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `res2.unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
+ --> $DIR/unnecessary_lazy_eval.rs:85:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| astronomers_pi);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
+ --> $DIR/unnecessary_lazy_eval.rs:86:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)`
+
+error: aborting due to 24 previous errors
+
--- /dev/null
+#![warn(clippy::unwrap_in_result)]
+
+struct A;
+
+impl A {
+ // should not be detected
+ fn good_divisible_by_3(i_str: String) -> Result<bool, String> {
+ // checks whether a string represents a number divisible by 3
+ let i_result = i_str.parse::<i32>();
+ match i_result {
+ Err(_e) => Err("Not a number".to_string()),
+ Ok(i) => {
+ if i % 3 == 0 {
+ return Ok(true);
+ }
+ Err("Number is not divisible by 3".to_string())
+ },
+ }
+ }
+
+ // should be detected
+ fn bad_divisible_by_3(i_str: String) -> Result<bool, String> {
+ // checks whether a string represents a number divisible by 3
+ let i = i_str.parse::<i32>().unwrap();
+ if i % 3 == 0 {
+ Ok(true)
+ } else {
+ Err("Number is not divisible by 3".to_string())
+ }
+ }
+
+ fn example_option_expect(i_str: String) -> Option<bool> {
+ let i = i_str.parse::<i32>().expect("not a number");
+ if i % 3 == 0 {
+ return Some(true);
+ }
+ None
+ }
+}
+
+fn main() {
+ A::bad_divisible_by_3("3".to_string());
+ A::good_divisible_by_3("3".to_string());
+}
--- /dev/null
+error: used unwrap or expect in a function that returns result or option
+ --> $DIR/unwrap_in_result.rs:22:5
+ |
+LL | / fn bad_divisible_by_3(i_str: String) -> Result<bool, String> {
+LL | | // checks whether a string represents a number divisible by 3
+LL | | let i = i_str.parse::<i32>().unwrap();
+LL | | if i % 3 == 0 {
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::unwrap-in-result` implied by `-D warnings`
+ = help: unwrap and expect should not be used in a function that returns result or option
+note: potential non-recoverable error(s)
+ --> $DIR/unwrap_in_result.rs:24:17
+ |
+LL | let i = i_str.parse::<i32>().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: used unwrap or expect in a function that returns result or option
+ --> $DIR/unwrap_in_result.rs:32:5
+ |
+LL | / fn example_option_expect(i_str: String) -> Option<bool> {
+LL | | let i = i_str.parse::<i32>().expect("not a number");
+LL | | if i % 3 == 0 {
+LL | | return Some(true);
+LL | | }
+LL | | None
+LL | | }
+ | |_____^
+ |
+ = help: unwrap and expect should not be used in a function that returns result or option
+note: potential non-recoverable error(s)
+ --> $DIR/unwrap_in_result.rs:33:17
+ |
+LL | let i = i_str.parse::<i32>().expect("not a number");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
let _ = "".lines();
let _ = vec![1, 2, 3].into_iter();
let _: String = format!("Hello {}", "world");
+
+ // keep parenthesis around `a + b` for suggestion (see #4750)
+ let a: i32 = 1;
+ let b: i32 = 1;
+ let _ = (a + b) * 3;
}
let _ = "".lines().into_iter();
let _ = vec![1, 2, 3].into_iter().into_iter();
let _: String = format!("Hello {}", "world").into();
+
+ // keep parenthesis around `a + b` for suggestion (see #4750)
+ let a: i32 = 1;
+ let b: i32 = 1;
+ let _ = i32::from(a + b) * 3;
}
LL | let _: String = format!("Hello {}", "world").into();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")`
-error: aborting due to 10 previous errors
+error: useless conversion to the same type
+ --> $DIR/useless_conversion.rs:71:13
+ |
+LL | let _ = i32::from(a + b) * 3;
+ | ^^^^^^^^^^^^^^^^ help: consider removing `i32::from()`: `(a + b)`
+
+error: aborting due to 11 previous errors
for a in vec![NonCopy, NonCopy] {
println!("{:?}", a);
}
+
+ on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+
+ // Ok
+ for a in vec![1; 201] {
+ println!("{:?}", a);
+ }
}
for a in vec![NonCopy, NonCopy] {
println!("{:?}", a);
}
+
+ on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+
+ // Ok
+ for a in vec![1; 201] {
+ println!("{:?}", a);
+ }
}
use wildcard_imports_helper::{ExternA, extern_foo};
use std::io::prelude::*;
+use wildcard_imports_helper::prelude::v1::*;
struct ReadFoo;
let _ = A;
let _ = inner_struct_mod::C;
let _ = ExternA;
+ let _ = PreludeModAnywhere;
double_struct_import_test!();
double_struct_import_test!();
use wildcard_imports_helper::*;
use std::io::prelude::*;
+use wildcard_imports_helper::prelude::v1::*;
struct ReadFoo;
let _ = A;
let _ = inner_struct_mod::C;
let _ = ExternA;
+ let _ = PreludeModAnywhere;
double_struct_import_test!();
double_struct_import_test!();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:89:13
+ --> $DIR/wildcard_imports.rs:91:13
|
LL | use crate::fn_mod::*;
| ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:95:75
+ --> $DIR/wildcard_imports.rs:97:75
|
LL | use wildcard_imports_helper::inner::inner_for_self_import::{self, *};
| ^ help: try: `inner_extern_foo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:96:13
+ --> $DIR/wildcard_imports.rs:98:13
|
LL | use wildcard_imports_helper::*;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:107:20
+ --> $DIR/wildcard_imports.rs:109:20
|
LL | use self::{inner::*, inner2::*};
| ^^^^^^^^ help: try: `inner::inner_foo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:107:30
+ --> $DIR/wildcard_imports.rs:109:30
|
LL | use self::{inner::*, inner2::*};
| ^^^^^^^^^ help: try: `inner2::inner_bar`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:114:13
+ --> $DIR/wildcard_imports.rs:116:13
|
LL | use wildcard_imports_helper::*;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:143:9
+ --> $DIR/wildcard_imports.rs:145:9
|
LL | use crate::in_fn_test::*;
| ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:152:9
+ --> $DIR/wildcard_imports.rs:154:9
|
LL | use crate:: in_fn_test:: * ;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:153:9
+ --> $DIR/wildcard_imports.rs:155:9
|
LL | use crate:: fn_mod::
| _________^
| |_________^ help: try: `crate:: fn_mod::foo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:164:13
+ --> $DIR/wildcard_imports.rs:166:13
|
LL | use super::*;
| ^^^^^^^^ help: try: `super::foofoo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:199:17
+ --> $DIR/wildcard_imports.rs:201:17
|
LL | use super::*;
| ^^^^^^^^ help: try: `super::insidefoo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:207:13
+ --> $DIR/wildcard_imports.rs:209:13
|
LL | use super_imports::*;
| ^^^^^^^^^^^^^^^^ help: try: `super_imports::foofoo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:216:17
+ --> $DIR/wildcard_imports.rs:218:17
|
LL | use super::super::*;
| ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo`
error: usage of wildcard import
- --> $DIR/wildcard_imports.rs:225:13
+ --> $DIR/wildcard_imports.rs:227:13
|
LL | use super::super::super_imports::*;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo`
+// edition:2018
#![warn(clippy::wrong_self_convention)]
#![warn(clippy::wrong_pub_self_convention)]
#![allow(dead_code)]
fn into_t3(self: Arc<T>) {}
}
}
+
+// False positive for async (see #4037)
+mod issue4037 {
+ pub struct Foo;
+ pub struct Bar;
+
+ impl Foo {
+ pub async fn into_bar(self) -> Bar {
+ Bar
+ }
+ }
+}
error: methods called `from_*` usually take no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:17:17
+ --> $DIR/wrong_self_convention.rs:18:17
|
LL | fn from_i32(self) {}
| ^^^^
= note: `-D clippy::wrong-self-convention` implied by `-D warnings`
error: methods called `from_*` usually take no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:23:21
+ --> $DIR/wrong_self_convention.rs:24:21
|
LL | pub fn from_i64(self) {}
| ^^^^
error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:35:15
+ --> $DIR/wrong_self_convention.rs:36:15
|
LL | fn as_i32(self) {}
| ^^^^
error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:37:17
+ --> $DIR/wrong_self_convention.rs:38:17
|
LL | fn into_i32(&self) {}
| ^^^^^
error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:39:15
+ --> $DIR/wrong_self_convention.rs:40:15
|
LL | fn is_i32(self) {}
| ^^^^
error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:41:15
+ --> $DIR/wrong_self_convention.rs:42:15
|
LL | fn to_i32(self) {}
| ^^^^
error: methods called `from_*` usually take no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:43:17
+ --> $DIR/wrong_self_convention.rs:44:17
|
LL | fn from_i32(self) {}
| ^^^^
error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:45:19
+ --> $DIR/wrong_self_convention.rs:46:19
|
LL | pub fn as_i64(self) {}
| ^^^^
error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:46:21
+ --> $DIR/wrong_self_convention.rs:47:21
|
LL | pub fn into_i64(&self) {}
| ^^^^^
error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:47:19
+ --> $DIR/wrong_self_convention.rs:48:19
|
LL | pub fn is_i64(self) {}
| ^^^^
error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:48:19
+ --> $DIR/wrong_self_convention.rs:49:19
|
LL | pub fn to_i64(self) {}
| ^^^^
error: methods called `from_*` usually take no self; consider choosing a less ambiguous name
- --> $DIR/wrong_self_convention.rs:49:21
+ --> $DIR/wrong_self_convention.rs:50:21
|
LL | pub fn from_i64(self) {}
| ^^^^
cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1");
}
+ if self.config.bless {
+ cmd.env("RUSTC_BLESS_TEST", "--bless");
+ // Assume this option is active if the environment variable is "defined", with _any_ value.
+ // As an example, a `Makefile` can use this option by:
+ //
+ // ifdef RUSTC_BLESS_TEST
+ // cp "$(TMPDIR)"/actual_something.ext expected_something.ext
+ // else
+ // $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
+ // endif
+ }
+
if self.config.target.contains("msvc") && self.config.cc != "" {
// We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
// and that `lib.exe` lives next to it.
-Subproject commit 2d6d73fafe2f087354f7cea37297cd81316cae98
+Subproject commit c2a2e25d0b050d70d6a355f9b7545a991fc8783a
return message
-def main():
+# Warning: Do not try to add a function containing the body of this try block.
+# There are variables declared within that are implicitly global; it is unknown
+# which ones precisely but at least this is true for `github_token`.
+try:
+ if __name__ != '__main__':
+ exit(0)
repo = os.environ.get('TOOLSTATE_VALIDATE_MAINTAINERS_REPO')
if repo:
github_token = os.environ.get('TOOLSTATE_REPO_ACCESS_TOKEN')
}
))
response.read()
-
-
-if __name__ == '__main__':
- try:
- main()
- except urllib2.HTTPError as e:
- print("HTTPError: %s\n%s" % (e, e.read()))
- raise
+except urllib2.HTTPError as e:
+ print("HTTPError: %s\n%s" % (e, e.read()))
+ raise
edition = "2018"
[dependencies]
-cargo_metadata = "0.9.1"
+cargo_metadata = "0.11"
regex = "1"
lazy_static = "1"
walkdir = "2"
("crossbeam-queue", "MIT/Apache-2.0 AND BSD-2-Clause"), // rls via rayon
("arrayref", "BSD-2-Clause"), // cargo-miri/directories/.../rust-argon2 (redox)
("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot
+ ("snap", "BSD-3-Clause"), // rustc
// FIXME: this dependency violates the documentation comment above:
("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target
];
"crossbeam-queue",
"crossbeam-utils",
"datafrog",
+ "difference",
"digest",
"dlmalloc",
"either",
"ena",
"env_logger",
+ "expect-test",
"fake-simd",
"filetime",
"flate2",
"serde_derive",
"sha-1",
"smallvec",
+ "snap",
"stable_deref_trait",
"stacker",
"syn",