Auto merge of #91469 - matthiaskrgr:rollup-xom3j55, r=matthiaskrgr
Rollup of 12 pull requests Successful merges: - #89954 (Fix legacy_const_generic doc arguments display) - #91321 (Handle placeholder regions in NLL type outlive constraints) - #91329 (Fix incorrect usage of `EvaluatedToOk` when evaluating `TypeOutlives`) - #91364 (Improve error message for incorrect field accesses through raw pointers) - #91387 (Clarify and tidy up explanation of E0038) - #91410 (Move `#![feature(const_precise_live_drops)]` checks earlier in the pipeline) - #91435 (Improve diagnostic for missing half of binary operator in `if` condition) - #91444 (disable tests in Miri that take too long) - #91457 (Add additional test from rust issue number 91068) - #91460 (Document how `last_os_error` should be used) - #91464 (Document file path case sensitivity) - #91466 (Improve the comments in `Symbol::interner`.) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
ff23ad3179
39 changed files with 707 additions and 81 deletions
|
@ -6,6 +6,7 @@ use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound};
|
||||||
use rustc_infer::infer::{self, InferCtxt, SubregionOrigin};
|
use rustc_infer::infer::{self, InferCtxt, SubregionOrigin};
|
||||||
use rustc_middle::mir::ConstraintCategory;
|
use rustc_middle::mir::ConstraintCategory;
|
||||||
use rustc_middle::ty::subst::GenericArgKind;
|
use rustc_middle::ty::subst::GenericArgKind;
|
||||||
|
use rustc_middle::ty::TypeFoldable;
|
||||||
use rustc_middle::ty::{self, TyCtxt};
|
use rustc_middle::ty::{self, TyCtxt};
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
|
|
||||||
|
@ -95,11 +96,23 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
|
||||||
self.add_outlives(r1_vid, r2_vid);
|
self.add_outlives(r1_vid, r2_vid);
|
||||||
}
|
}
|
||||||
|
|
||||||
GenericArgKind::Type(t1) => {
|
GenericArgKind::Type(mut t1) => {
|
||||||
// we don't actually use this for anything, but
|
// we don't actually use this for anything, but
|
||||||
// the `TypeOutlives` code needs an origin.
|
// the `TypeOutlives` code needs an origin.
|
||||||
let origin = infer::RelateParamBound(DUMMY_SP, t1, None);
|
let origin = infer::RelateParamBound(DUMMY_SP, t1, None);
|
||||||
|
|
||||||
|
// Placeholder regions need to be converted now because it may
|
||||||
|
// create new region variables, which can't be done later when
|
||||||
|
// verifying these bounds.
|
||||||
|
if t1.has_placeholders() {
|
||||||
|
t1 = tcx.fold_regions(&t1, &mut false, |r, _| match *r {
|
||||||
|
ty::RegionKind::RePlaceholder(placeholder) => {
|
||||||
|
self.constraints.placeholder_region(self.infcx, placeholder)
|
||||||
|
}
|
||||||
|
_ => r,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
TypeOutlives::new(
|
TypeOutlives::new(
|
||||||
&mut *self,
|
&mut *self,
|
||||||
tcx,
|
tcx,
|
||||||
|
|
|
@ -80,7 +80,8 @@ impl Visitor<'tcx> for CheckLiveDrops<'mir, 'tcx> {
|
||||||
trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
|
trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
|
||||||
|
|
||||||
match &terminator.kind {
|
match &terminator.kind {
|
||||||
mir::TerminatorKind::Drop { place: dropped_place, .. } => {
|
mir::TerminatorKind::Drop { place: dropped_place, .. }
|
||||||
|
| mir::TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
|
||||||
let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
|
let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
|
||||||
if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
|
if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
|
||||||
// Instead of throwing a bug, we just return here. This is because we have to
|
// Instead of throwing a bug, we just return here. This is because we have to
|
||||||
|
@ -104,11 +105,6 @@ impl Visitor<'tcx> for CheckLiveDrops<'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mir::TerminatorKind::DropAndReplace { .. } => span_bug!(
|
|
||||||
terminator.source_info.span,
|
|
||||||
"`DropAndReplace` should be removed by drop elaboration",
|
|
||||||
),
|
|
||||||
|
|
||||||
mir::TerminatorKind::Abort
|
mir::TerminatorKind::Abort
|
||||||
| mir::TerminatorKind::Call { .. }
|
| mir::TerminatorKind::Call { .. }
|
||||||
| mir::TerminatorKind::Assert { .. }
|
| mir::TerminatorKind::Assert { .. }
|
||||||
|
|
|
@ -1,34 +1,64 @@
|
||||||
Trait objects like `Box<Trait>` can only be constructed when certain
|
For any given trait `Trait` there may be a related _type_ called the _trait
|
||||||
requirements are satisfied by the trait in question.
|
object type_ which is typically written as `dyn Trait`. In earlier editions of
|
||||||
|
Rust, trait object types were written as plain `Trait` (just the name of the
|
||||||
|
trait, written in type positions) but this was a bit too confusing, so we now
|
||||||
|
write `dyn Trait`.
|
||||||
|
|
||||||
Trait objects are a form of dynamic dispatch and use a dynamically sized type
|
Some traits are not allowed to be used as trait object types. The traits that
|
||||||
for the inner type. So, for a given trait `Trait`, when `Trait` is treated as a
|
are allowed to be used as trait object types are called "object-safe" traits.
|
||||||
type, as in `Box<Trait>`, the inner type is 'unsized'. In such cases the boxed
|
Attempting to use a trait object type for a trait that is not object-safe will
|
||||||
pointer is a 'fat pointer' that contains an extra pointer to a table of methods
|
trigger error E0038.
|
||||||
(among other things) for dynamic dispatch. This design mandates some
|
|
||||||
restrictions on the types of traits that are allowed to be used in trait
|
|
||||||
objects, which are collectively termed as 'object safety' rules.
|
|
||||||
|
|
||||||
Attempting to create a trait object for a non object-safe trait will trigger
|
Two general aspects of trait object types give rise to the restrictions:
|
||||||
this error.
|
|
||||||
|
|
||||||
There are various rules:
|
1. Trait object types are dynamically sized types (DSTs), and trait objects of
|
||||||
|
these types can only be accessed through pointers, such as `&dyn Trait` or
|
||||||
|
`Box<dyn Trait>`. The size of such a pointer is known, but the size of the
|
||||||
|
`dyn Trait` object pointed-to by the pointer is _opaque_ to code working
|
||||||
|
with it, and different tait objects with the same trait object type may
|
||||||
|
have different sizes.
|
||||||
|
|
||||||
### The trait cannot require `Self: Sized`
|
2. The pointer used to access a trait object is paired with an extra pointer
|
||||||
|
to a "virtual method table" or "vtable", which is used to implement dynamic
|
||||||
|
dispatch to the object's implementations of the trait's methods. There is a
|
||||||
|
single such vtable for each trait implementation, but different trait
|
||||||
|
objects with the same trait object type may point to vtables from different
|
||||||
|
implementations.
|
||||||
|
|
||||||
When `Trait` is treated as a type, the type does not implement the special
|
The specific conditions that violate object-safety follow, most of which relate
|
||||||
`Sized` trait, because the type does not have a known size at compile time and
|
to missing size information and vtable polymorphism arising from these aspects.
|
||||||
can only be accessed behind a pointer. Thus, if we have a trait like the
|
|
||||||
following:
|
### The trait requires `Self: Sized`
|
||||||
|
|
||||||
|
Traits that are declared as `Trait: Sized` or which otherwise inherit a
|
||||||
|
constraint of `Self:Sized` are not object-safe.
|
||||||
|
|
||||||
|
The reasoning behind this is somewhat subtle. It derives from the fact that Rust
|
||||||
|
requires (and defines) that every trait object type `dyn Trait` automatically
|
||||||
|
implements `Trait`. Rust does this to simplify error reporting and ease
|
||||||
|
interoperation between static and dynamic polymorphism. For example, this code
|
||||||
|
works:
|
||||||
|
|
||||||
```
|
```
|
||||||
trait Foo where Self: Sized {
|
trait Trait {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_foo<T:Trait + ?Sized>(b: &T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dynamic_bar(a: &dyn Trait) {
|
||||||
|
static_foo(a)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We cannot create an object of type `Box<Foo>` or `&Foo` since in this case
|
This code works because `dyn Trait`, if it exists, always implements `Trait`.
|
||||||
`Self` would not be `Sized`.
|
|
||||||
|
However as we know, any `dyn Trait` is also unsized, and so it can never
|
||||||
|
implement a sized trait like `Trait:Sized`. So, rather than allow an exception
|
||||||
|
to the rule that `dyn Trait` always implements `Trait`, Rust chooses to prohibit
|
||||||
|
such a `dyn Trait` from existing at all.
|
||||||
|
|
||||||
|
Only unsized traits are considered object-safe.
|
||||||
|
|
||||||
Generally, `Self: Sized` is used to indicate that the trait should not be used
|
Generally, `Self: Sized` is used to indicate that the trait should not be used
|
||||||
as a trait object. If the trait comes from your own crate, consider removing
|
as a trait object. If the trait comes from your own crate, consider removing
|
||||||
|
@ -67,7 +97,7 @@ trait Trait {
|
||||||
fn foo(&self) -> Self;
|
fn foo(&self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_foo(x: Box<Trait>) {
|
fn call_foo(x: Box<dyn Trait>) {
|
||||||
let y = x.foo(); // What type is y?
|
let y = x.foo(); // What type is y?
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
@ -76,7 +106,8 @@ fn call_foo(x: Box<Trait>) {
|
||||||
If only some methods aren't object-safe, you can add a `where Self: Sized` bound
|
If only some methods aren't object-safe, you can add a `where Self: Sized` bound
|
||||||
on them to mark them as explicitly unavailable to trait objects. The
|
on them to mark them as explicitly unavailable to trait objects. The
|
||||||
functionality will still be available to all other implementers, including
|
functionality will still be available to all other implementers, including
|
||||||
`Box<Trait>` which is itself sized (assuming you `impl Trait for Box<Trait>`).
|
`Box<dyn Trait>` which is itself sized (assuming you `impl Trait for Box<dyn
|
||||||
|
Trait>`).
|
||||||
|
|
||||||
```
|
```
|
||||||
trait Trait {
|
trait Trait {
|
||||||
|
@ -115,7 +146,9 @@ impl Trait for u8 {
|
||||||
```
|
```
|
||||||
|
|
||||||
At compile time each implementation of `Trait` will produce a table containing
|
At compile time each implementation of `Trait` will produce a table containing
|
||||||
the various methods (and other items) related to the implementation.
|
the various methods (and other items) related to the implementation, which will
|
||||||
|
be used as the virtual method table for a `dyn Trait` object derived from that
|
||||||
|
implementation.
|
||||||
|
|
||||||
This works fine, but when the method gains generic parameters, we can have a
|
This works fine, but when the method gains generic parameters, we can have a
|
||||||
problem.
|
problem.
|
||||||
|
@ -174,7 +207,7 @@ Now, if we have the following code:
|
||||||
# impl Trait for u8 { fn foo<T>(&self, on: T) {} }
|
# impl Trait for u8 { fn foo<T>(&self, on: T) {} }
|
||||||
# impl Trait for bool { fn foo<T>(&self, on: T) {} }
|
# impl Trait for bool { fn foo<T>(&self, on: T) {} }
|
||||||
# // etc.
|
# // etc.
|
||||||
fn call_foo(thing: Box<Trait>) {
|
fn call_foo(thing: Box<dyn Trait>) {
|
||||||
thing.foo(true); // this could be any one of the 8 types above
|
thing.foo(true); // this could be any one of the 8 types above
|
||||||
thing.foo(1);
|
thing.foo(1);
|
||||||
thing.foo("hello");
|
thing.foo("hello");
|
||||||
|
@ -200,7 +233,7 @@ trait Trait {
|
||||||
```
|
```
|
||||||
|
|
||||||
If this is not an option, consider replacing the type parameter with another
|
If this is not an option, consider replacing the type parameter with another
|
||||||
trait object (e.g., if `T: OtherTrait`, use `on: Box<OtherTrait>`). If the
|
trait object (e.g., if `T: OtherTrait`, use `on: Box<dyn OtherTrait>`). If the
|
||||||
number of types you intend to feed to this method is limited, consider manually
|
number of types you intend to feed to this method is limited, consider manually
|
||||||
listing out the methods of different types.
|
listing out the methods of different types.
|
||||||
|
|
||||||
|
@ -226,7 +259,7 @@ trait Foo {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### The trait cannot contain associated constants
|
### Trait contains associated constants
|
||||||
|
|
||||||
Just like static functions, associated constants aren't stored on the method
|
Just like static functions, associated constants aren't stored on the method
|
||||||
table. If the trait or any subtrait contain an associated constant, they cannot
|
table. If the trait or any subtrait contain an associated constant, they cannot
|
||||||
|
@ -248,7 +281,7 @@ trait Foo {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### The trait cannot use `Self` as a type parameter in the supertrait listing
|
### Trait uses `Self` as a type parameter in the supertrait listing
|
||||||
|
|
||||||
This is similar to the second sub-error, but subtler. It happens in situations
|
This is similar to the second sub-error, but subtler. It happens in situations
|
||||||
like the following:
|
like the following:
|
||||||
|
|
|
@ -1803,6 +1803,16 @@ impl<V, T> ProjectionElem<V, T> {
|
||||||
| Self::Downcast(_, _) => false,
|
| Self::Downcast(_, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`.
|
||||||
|
pub fn is_downcast_to(&self, v: VariantIdx) -> bool {
|
||||||
|
matches!(*self, Self::Downcast(_, x) if x == v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is a `Field` projection with the given index.
|
||||||
|
pub fn is_field_to(&self, f: Field) -> bool {
|
||||||
|
matches!(*self, Self::Field(x, _) if x == f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for projections as they appear in places, where the base is a place
|
/// Alias for projections as they appear in places, where the base is a place
|
||||||
|
|
|
@ -60,8 +60,10 @@ mod match_branches;
|
||||||
mod multiple_return_terminators;
|
mod multiple_return_terminators;
|
||||||
mod normalize_array_len;
|
mod normalize_array_len;
|
||||||
mod nrvo;
|
mod nrvo;
|
||||||
|
mod remove_false_edges;
|
||||||
mod remove_noop_landing_pads;
|
mod remove_noop_landing_pads;
|
||||||
mod remove_storage_markers;
|
mod remove_storage_markers;
|
||||||
|
mod remove_uninit_drops;
|
||||||
mod remove_unneeded_drops;
|
mod remove_unneeded_drops;
|
||||||
mod remove_zsts;
|
mod remove_zsts;
|
||||||
mod required_consts;
|
mod required_consts;
|
||||||
|
@ -75,7 +77,7 @@ mod simplify_try;
|
||||||
mod uninhabited_enum_branching;
|
mod uninhabited_enum_branching;
|
||||||
mod unreachable_prop;
|
mod unreachable_prop;
|
||||||
|
|
||||||
use rustc_const_eval::transform::check_consts;
|
use rustc_const_eval::transform::check_consts::{self, ConstCx};
|
||||||
use rustc_const_eval::transform::promote_consts;
|
use rustc_const_eval::transform::promote_consts;
|
||||||
use rustc_const_eval::transform::validate;
|
use rustc_const_eval::transform::validate;
|
||||||
use rustc_mir_dataflow::rustc_peek;
|
use rustc_mir_dataflow::rustc_peek;
|
||||||
|
@ -444,8 +446,20 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
|
||||||
let (body, _) = tcx.mir_promoted(def);
|
let (body, _) = tcx.mir_promoted(def);
|
||||||
let mut body = body.steal();
|
let mut body = body.steal();
|
||||||
|
|
||||||
run_post_borrowck_cleanup_passes(tcx, &mut body);
|
// IMPORTANT
|
||||||
|
remove_false_edges::RemoveFalseEdges.run_pass(tcx, &mut body);
|
||||||
|
|
||||||
|
// Do a little drop elaboration before const-checking if `const_precise_live_drops` is enabled.
|
||||||
|
//
|
||||||
|
// FIXME: Can't use `run_passes` for these, since `run_passes` SILENTLY DOES NOTHING IF THE MIR
|
||||||
|
// PHASE DOESN'T CHANGE.
|
||||||
|
if check_consts::post_drop_elaboration::checking_enabled(&ConstCx::new(tcx, &body)) {
|
||||||
|
simplify::SimplifyCfg::new("remove-false-edges").run_pass(tcx, &mut body);
|
||||||
|
remove_uninit_drops::RemoveUninitDrops.run_pass(tcx, &mut body);
|
||||||
check_consts::post_drop_elaboration::check_live_drops(tcx, &body);
|
check_consts::post_drop_elaboration::check_live_drops(tcx, &body);
|
||||||
|
}
|
||||||
|
|
||||||
|
run_post_borrowck_cleanup_passes(tcx, &mut body);
|
||||||
tcx.alloc_steal_mir(body)
|
tcx.alloc_steal_mir(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +469,7 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
|
||||||
|
|
||||||
let post_borrowck_cleanup: &[&dyn MirPass<'tcx>] = &[
|
let post_borrowck_cleanup: &[&dyn MirPass<'tcx>] = &[
|
||||||
// Remove all things only needed by analysis
|
// Remove all things only needed by analysis
|
||||||
&simplify_branches::SimplifyBranches::new("initial"),
|
&simplify_branches::SimplifyConstCondition::new("initial"),
|
||||||
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
||||||
&cleanup_post_borrowck::CleanupNonCodegenStatements,
|
&cleanup_post_borrowck::CleanupNonCodegenStatements,
|
||||||
&simplify::SimplifyCfg::new("early-opt"),
|
&simplify::SimplifyCfg::new("early-opt"),
|
||||||
|
@ -514,13 +528,13 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||||
&instcombine::InstCombine,
|
&instcombine::InstCombine,
|
||||||
&separate_const_switch::SeparateConstSwitch,
|
&separate_const_switch::SeparateConstSwitch,
|
||||||
&const_prop::ConstProp,
|
&const_prop::ConstProp,
|
||||||
&simplify_branches::SimplifyBranches::new("after-const-prop"),
|
&simplify_branches::SimplifyConstCondition::new("after-const-prop"),
|
||||||
&early_otherwise_branch::EarlyOtherwiseBranch,
|
&early_otherwise_branch::EarlyOtherwiseBranch,
|
||||||
&simplify_comparison_integral::SimplifyComparisonIntegral,
|
&simplify_comparison_integral::SimplifyComparisonIntegral,
|
||||||
&simplify_try::SimplifyArmIdentity,
|
&simplify_try::SimplifyArmIdentity,
|
||||||
&simplify_try::SimplifyBranchSame,
|
&simplify_try::SimplifyBranchSame,
|
||||||
&dest_prop::DestinationPropagation,
|
&dest_prop::DestinationPropagation,
|
||||||
&simplify_branches::SimplifyBranches::new("final"),
|
&simplify_branches::SimplifyConstCondition::new("final"),
|
||||||
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
||||||
&simplify::SimplifyCfg::new("final"),
|
&simplify::SimplifyCfg::new("final"),
|
||||||
&nrvo::RenameReturnPlace,
|
&nrvo::RenameReturnPlace,
|
||||||
|
|
29
compiler/rustc_mir_transform/src/remove_false_edges.rs
Normal file
29
compiler/rustc_mir_transform/src/remove_false_edges.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use rustc_middle::mir::{Body, TerminatorKind};
|
||||||
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
|
||||||
|
use crate::MirPass;
|
||||||
|
|
||||||
|
/// Removes `FalseEdge` and `FalseUnwind` terminators from the MIR.
|
||||||
|
///
|
||||||
|
/// These are only needed for borrow checking, and can be removed afterwards.
|
||||||
|
///
|
||||||
|
/// FIXME: This should probably have its own MIR phase.
|
||||||
|
pub struct RemoveFalseEdges;
|
||||||
|
|
||||||
|
impl<'tcx> MirPass<'tcx> for RemoveFalseEdges {
|
||||||
|
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||||
|
for block in body.basic_blocks_mut() {
|
||||||
|
let terminator = block.terminator_mut();
|
||||||
|
terminator.kind = match terminator.kind {
|
||||||
|
TerminatorKind::FalseEdge { real_target, .. } => {
|
||||||
|
TerminatorKind::Goto { target: real_target }
|
||||||
|
}
|
||||||
|
TerminatorKind::FalseUnwind { real_target, .. } => {
|
||||||
|
TerminatorKind::Goto { target: real_target }
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
compiler/rustc_mir_transform/src/remove_uninit_drops.rs
Normal file
171
compiler/rustc_mir_transform/src/remove_uninit_drops.rs
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
use rustc_index::bit_set::BitSet;
|
||||||
|
use rustc_middle::mir::{Body, Field, Rvalue, Statement, StatementKind, TerminatorKind};
|
||||||
|
use rustc_middle::ty::subst::SubstsRef;
|
||||||
|
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, VariantDef};
|
||||||
|
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
|
||||||
|
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
|
||||||
|
use rustc_mir_dataflow::{self, move_path_children_matching, Analysis, MoveDataParamEnv};
|
||||||
|
|
||||||
|
use crate::MirPass;
|
||||||
|
|
||||||
|
/// Removes `Drop` and `DropAndReplace` terminators whose target is known to be uninitialized at
|
||||||
|
/// that point.
|
||||||
|
///
|
||||||
|
/// This is redundant with drop elaboration, but we need to do it prior to const-checking, and
|
||||||
|
/// running const-checking after drop elaboration makes it opimization dependent, causing issues
|
||||||
|
/// like [#90770].
|
||||||
|
///
|
||||||
|
/// [#90770]: https://github.com/rust-lang/rust/issues/90770
|
||||||
|
pub struct RemoveUninitDrops;
|
||||||
|
|
||||||
|
impl<'tcx> MirPass<'tcx> for RemoveUninitDrops {
|
||||||
|
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||||
|
let param_env = tcx.param_env(body.source.def_id());
|
||||||
|
let Ok(move_data) = MoveData::gather_moves(body, tcx, param_env) else {
|
||||||
|
// We could continue if there are move errors, but there's not much point since our
|
||||||
|
// init data isn't complete.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mdpe = MoveDataParamEnv { move_data, param_env };
|
||||||
|
let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
|
||||||
|
.into_engine(tcx, body)
|
||||||
|
.pass_name("remove_uninit_drops")
|
||||||
|
.iterate_to_fixpoint()
|
||||||
|
.into_results_cursor(body);
|
||||||
|
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (bb, block) in body.basic_blocks().iter_enumerated() {
|
||||||
|
let terminator = block.terminator();
|
||||||
|
let (TerminatorKind::Drop { place, .. } | TerminatorKind::DropAndReplace { place, .. })
|
||||||
|
= &terminator.kind
|
||||||
|
else { continue };
|
||||||
|
|
||||||
|
maybe_inits.seek_before_primary_effect(body.terminator_loc(bb));
|
||||||
|
|
||||||
|
// If there's no move path for the dropped place, it's probably a `Deref`. Let it alone.
|
||||||
|
let LookupResult::Exact(mpi) = mdpe.move_data.rev_lookup.find(place.as_ref()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let should_keep = is_needs_drop_and_init(
|
||||||
|
tcx,
|
||||||
|
param_env,
|
||||||
|
maybe_inits.get(),
|
||||||
|
&mdpe.move_data,
|
||||||
|
place.ty(body, tcx).ty,
|
||||||
|
mpi,
|
||||||
|
);
|
||||||
|
if !should_keep {
|
||||||
|
to_remove.push(bb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for bb in to_remove {
|
||||||
|
let block = &mut body.basic_blocks_mut()[bb];
|
||||||
|
|
||||||
|
let (TerminatorKind::Drop { target, .. } | TerminatorKind::DropAndReplace { target, .. })
|
||||||
|
= &block.terminator().kind
|
||||||
|
else { unreachable!() };
|
||||||
|
|
||||||
|
// Replace block terminator with `Goto`.
|
||||||
|
let target = *target;
|
||||||
|
let old_terminator_kind = std::mem::replace(
|
||||||
|
&mut block.terminator_mut().kind,
|
||||||
|
TerminatorKind::Goto { target },
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this is a `DropAndReplace`, we need to emulate the assignment to the return place.
|
||||||
|
if let TerminatorKind::DropAndReplace { place, value, .. } = old_terminator_kind {
|
||||||
|
block.statements.push(Statement {
|
||||||
|
source_info: block.terminator().source_info,
|
||||||
|
kind: StatementKind::Assign(Box::new((place, Rvalue::Use(value)))),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_needs_drop_and_init(
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
param_env: ParamEnv<'tcx>,
|
||||||
|
maybe_inits: &BitSet<MovePathIndex>,
|
||||||
|
move_data: &MoveData<'tcx>,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
mpi: MovePathIndex,
|
||||||
|
) -> bool {
|
||||||
|
// No need to look deeper if the root is definitely uninit or if it has no `Drop` impl.
|
||||||
|
if !maybe_inits.contains(mpi) || !ty.needs_drop(tcx, param_env) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let field_needs_drop_and_init = |(f, f_ty, mpi)| {
|
||||||
|
let child = move_path_children_matching(move_data, mpi, |x| x.is_field_to(f));
|
||||||
|
let Some(mpi) = child else {
|
||||||
|
return f_ty.needs_drop(tcx, param_env);
|
||||||
|
};
|
||||||
|
|
||||||
|
is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)
|
||||||
|
};
|
||||||
|
|
||||||
|
// This pass is only needed for const-checking, so it doesn't handle as many cases as
|
||||||
|
// `DropCtxt::open_drop`, since they aren't relevant in a const-context.
|
||||||
|
match ty.kind() {
|
||||||
|
ty::Adt(adt, substs) => {
|
||||||
|
let dont_elaborate = adt.is_union() || adt.is_manually_drop() || adt.has_dtor(tcx);
|
||||||
|
if dont_elaborate {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look at all our fields, or if we are an enum all our variants and their fields.
|
||||||
|
//
|
||||||
|
// If a field's projection *is not* present in `MoveData`, it has the same
|
||||||
|
// initializedness as its parent (maybe init).
|
||||||
|
//
|
||||||
|
// If its projection *is* present in `MoveData`, then the field may have been moved
|
||||||
|
// from separate from its parent. Recurse.
|
||||||
|
adt.variants.iter_enumerated().any(|(vid, variant)| {
|
||||||
|
// Enums have multiple variants, which are discriminated with a `Downcast` projection.
|
||||||
|
// Structs have a single variant, and don't use a `Downcast` projection.
|
||||||
|
let mpi = if adt.is_enum() {
|
||||||
|
let downcast =
|
||||||
|
move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid));
|
||||||
|
let Some(dc_mpi) = downcast else {
|
||||||
|
return variant_needs_drop(tcx, param_env, substs, variant);
|
||||||
|
};
|
||||||
|
|
||||||
|
dc_mpi
|
||||||
|
} else {
|
||||||
|
mpi
|
||||||
|
};
|
||||||
|
|
||||||
|
variant
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(f, field)| (Field::from_usize(f), field.ty(tcx, substs), mpi))
|
||||||
|
.any(field_needs_drop_and_init)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ty::Tuple(_) => ty
|
||||||
|
.tuple_fields()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(f, f_ty)| (Field::from_usize(f), f_ty, mpi))
|
||||||
|
.any(field_needs_drop_and_init),
|
||||||
|
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variant_needs_drop(
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
param_env: ParamEnv<'tcx>,
|
||||||
|
substs: SubstsRef<'tcx>,
|
||||||
|
variant: &VariantDef,
|
||||||
|
) -> bool {
|
||||||
|
variant.fields.iter().any(|field| {
|
||||||
|
let f_ty = field.ty(tcx, substs);
|
||||||
|
f_ty.needs_drop(tcx, param_env)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
//! This pass replaces a drop of a type that does not need dropping, with a goto
|
//! This pass replaces a drop of a type that does not need dropping, with a goto.
|
||||||
|
//!
|
||||||
|
//! When the MIR is built, we check `needs_drop` before emitting a `Drop` for a place. This pass is
|
||||||
|
//! useful because (unlike MIR building) it runs after type checking, so it can make use of
|
||||||
|
//! `Reveal::All` to provide more precies type information.
|
||||||
|
|
||||||
use crate::MirPass;
|
use crate::MirPass;
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
//! A pass that simplifies branches when their condition is known.
|
|
||||||
|
|
||||||
use crate::MirPass;
|
use crate::MirPass;
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub struct SimplifyBranches {
|
/// A pass that replaces a branch with a goto when its condition is known.
|
||||||
|
pub struct SimplifyConstCondition {
|
||||||
label: String,
|
label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimplifyBranches {
|
impl SimplifyConstCondition {
|
||||||
pub fn new(label: &str) -> Self {
|
pub fn new(label: &str) -> Self {
|
||||||
SimplifyBranches { label: format!("SimplifyBranches-{}", label) }
|
SimplifyConstCondition { label: format!("SimplifyConstCondition-{}", label) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> MirPass<'tcx> for SimplifyBranches {
|
impl<'tcx> MirPass<'tcx> for SimplifyConstCondition {
|
||||||
fn name(&self) -> Cow<'_, str> {
|
fn name(&self) -> Cow<'_, str> {
|
||||||
Cow::Borrowed(&self.label)
|
Cow::Borrowed(&self.label)
|
||||||
}
|
}
|
||||||
|
@ -53,12 +52,6 @@ impl<'tcx> MirPass<'tcx> for SimplifyBranches {
|
||||||
Some(v) if v == expected => TerminatorKind::Goto { target },
|
Some(v) if v == expected => TerminatorKind::Goto { target },
|
||||||
_ => continue,
|
_ => continue,
|
||||||
},
|
},
|
||||||
TerminatorKind::FalseEdge { real_target, .. } => {
|
|
||||||
TerminatorKind::Goto { target: real_target }
|
|
||||||
}
|
|
||||||
TerminatorKind::FalseUnwind { real_target, .. } => {
|
|
||||||
TerminatorKind::Goto { target: real_target }
|
|
||||||
}
|
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1988,25 +1988,34 @@ impl<'a> Parser<'a> {
|
||||||
let lo = self.prev_token.span;
|
let lo = self.prev_token.span;
|
||||||
let cond = self.parse_cond_expr()?;
|
let cond = self.parse_cond_expr()?;
|
||||||
|
|
||||||
|
let missing_then_block_binop_span = || {
|
||||||
|
match cond.kind {
|
||||||
|
ExprKind::Binary(Spanned { span: binop_span, .. }, _, ref right)
|
||||||
|
if let ExprKind::Block(..) = right.kind => Some(binop_span),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Verify that the parsed `if` condition makes sense as a condition. If it is a block, then
|
// Verify that the parsed `if` condition makes sense as a condition. If it is a block, then
|
||||||
// verify that the last statement is either an implicit return (no `;`) or an explicit
|
// verify that the last statement is either an implicit return (no `;`) or an explicit
|
||||||
// return. This won't catch blocks with an explicit `return`, but that would be caught by
|
// return. This won't catch blocks with an explicit `return`, but that would be caught by
|
||||||
// the dead code lint.
|
// the dead code lint.
|
||||||
let thn = if self.eat_keyword(kw::Else) || !cond.returns() {
|
let thn = if self.token.is_keyword(kw::Else) || !cond.returns() {
|
||||||
|
if let Some(binop_span) = missing_then_block_binop_span() {
|
||||||
|
self.error_missing_if_then_block(lo, None, Some(binop_span)).emit();
|
||||||
|
self.mk_block_err(cond.span)
|
||||||
|
} else {
|
||||||
self.error_missing_if_cond(lo, cond.span)
|
self.error_missing_if_cond(lo, cond.span)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery.
|
let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery.
|
||||||
let not_block = self.token != token::OpenDelim(token::Brace);
|
let not_block = self.token != token::OpenDelim(token::Brace);
|
||||||
let block = self.parse_block().map_err(|mut err| {
|
let block = self.parse_block().map_err(|err| {
|
||||||
if not_block {
|
if not_block {
|
||||||
err.span_label(lo, "this `if` expression has a condition, but no block");
|
self.error_missing_if_then_block(lo, Some(err), missing_then_block_binop_span())
|
||||||
if let ExprKind::Binary(_, _, ref right) = cond.kind {
|
} else {
|
||||||
if let ExprKind::Block(_, _) = right.kind {
|
|
||||||
err.help("maybe you forgot the right operand of the condition?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err
|
err
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
self.error_on_if_block_attrs(lo, false, block.span, &attrs);
|
self.error_on_if_block_attrs(lo, false, block.span, &attrs);
|
||||||
block
|
block
|
||||||
|
@ -2015,6 +2024,28 @@ impl<'a> Parser<'a> {
|
||||||
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els), attrs))
|
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els), attrs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error_missing_if_then_block(
|
||||||
|
&self,
|
||||||
|
if_span: Span,
|
||||||
|
err: Option<DiagnosticBuilder<'a>>,
|
||||||
|
binop_span: Option<Span>,
|
||||||
|
) -> DiagnosticBuilder<'a> {
|
||||||
|
let msg = "this `if` expression has a condition, but no block";
|
||||||
|
|
||||||
|
let mut err = if let Some(mut err) = err {
|
||||||
|
err.span_label(if_span, msg);
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
self.struct_span_err(if_span, msg)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(binop_span) = binop_span {
|
||||||
|
err.span_help(binop_span, "maybe you forgot the right operand of the condition?");
|
||||||
|
}
|
||||||
|
|
||||||
|
err
|
||||||
|
}
|
||||||
|
|
||||||
fn error_missing_if_cond(&self, lo: Span, span: Span) -> P<ast::Block> {
|
fn error_missing_if_cond(&self, lo: Span, span: Span) -> P<ast::Block> {
|
||||||
let sp = self.sess.source_map().next_point(lo);
|
let sp = self.sess.source_map().next_point(lo);
|
||||||
self.struct_span_err(sp, "missing condition for `if` expression")
|
self.struct_span_err(sp, "missing condition for `if` expression")
|
||||||
|
|
|
@ -1716,8 +1716,9 @@ pub(crate) struct Interner(Lock<InternerInner>);
|
||||||
// found that to regress performance up to 2% in some cases. This might be
|
// found that to regress performance up to 2% in some cases. This might be
|
||||||
// revisited after further improvements to `indexmap`.
|
// revisited after further improvements to `indexmap`.
|
||||||
//
|
//
|
||||||
// This type is private to prevent accidentally constructing more than one `Interner` on the same
|
// This type is private to prevent accidentally constructing more than one
|
||||||
// thread, which makes it easy to mixup `Symbol`s between `Interner`s.
|
// `Interner` on the same thread, which makes it easy to mixup `Symbol`s
|
||||||
|
// between `Interner`s.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InternerInner {
|
struct InternerInner {
|
||||||
arena: DroplessArena,
|
arena: DroplessArena,
|
||||||
|
@ -1743,14 +1744,20 @@ impl Interner {
|
||||||
|
|
||||||
let name = Symbol::new(inner.strings.len() as u32);
|
let name = Symbol::new(inner.strings.len() as u32);
|
||||||
|
|
||||||
// `from_utf8_unchecked` is safe since we just allocated a `&str` which is known to be
|
// SAFETY: we convert from `&str` to `&[u8]`, clone it into the arena,
|
||||||
// UTF-8.
|
// and immediately convert the clone back to `&[u8], all because there
|
||||||
|
// is no `inner.arena.alloc_str()` method. This is clearly safe.
|
||||||
let string: &str =
|
let string: &str =
|
||||||
unsafe { str::from_utf8_unchecked(inner.arena.alloc_slice(string.as_bytes())) };
|
unsafe { str::from_utf8_unchecked(inner.arena.alloc_slice(string.as_bytes())) };
|
||||||
// It is safe to extend the arena allocation to `'static` because we only access
|
|
||||||
// these while the arena is still alive.
|
// SAFETY: we can extend the arena allocation to `'static` because we
|
||||||
|
// only access these while the arena is still alive.
|
||||||
let string: &'static str = unsafe { &*(string as *const str) };
|
let string: &'static str = unsafe { &*(string as *const str) };
|
||||||
inner.strings.push(string);
|
inner.strings.push(string);
|
||||||
|
|
||||||
|
// This second hash table lookup can be avoided by using `RawEntryMut`,
|
||||||
|
// but this code path isn't hot enough for it to be worth it. See
|
||||||
|
// #91445 for details.
|
||||||
inner.names.insert(string, name);
|
inner.names.insert(string, name);
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
|
@ -521,7 +521,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
},
|
},
|
||||||
|
|
||||||
ty::PredicateKind::TypeOutlives(pred) => {
|
ty::PredicateKind::TypeOutlives(pred) => {
|
||||||
if pred.0.is_known_global() {
|
// A global type with no late-bound regions can only
|
||||||
|
// contain the "'static" lifetime (any other lifetime
|
||||||
|
// would either be late-bound or local), so it is guaranteed
|
||||||
|
// to outlive any other lifetime
|
||||||
|
if pred.0.is_global(self.infcx.tcx) && !pred.0.has_late_bound_regions() {
|
||||||
Ok(EvaluatedToOk)
|
Ok(EvaluatedToOk)
|
||||||
} else {
|
} else {
|
||||||
Ok(EvaluatedToOkModuloRegions)
|
Ok(EvaluatedToOkModuloRegions)
|
||||||
|
|
|
@ -46,6 +46,7 @@ use rustc_span::hygiene::DesugaringKind;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
use rustc_span::lev_distance::find_best_match_for_name;
|
||||||
use rustc_span::source_map::Span;
|
use rustc_span::source_map::Span;
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
|
use rustc_span::{BytePos, Pos};
|
||||||
use rustc_trait_selection::traits::{self, ObligationCauseCode};
|
use rustc_trait_selection::traits::{self, ObligationCauseCode};
|
||||||
|
|
||||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
|
@ -2063,8 +2064,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
Some(span),
|
Some(span),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
if let ty::RawPtr(ty_and_mut) = expr_t.kind() {
|
||||||
|
if let ty::Adt(adt_def, _) = ty_and_mut.ty.kind() {
|
||||||
|
if adt_def.variants.len() == 1
|
||||||
|
&& adt_def
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.any(|f| f.ident == field)
|
||||||
|
{
|
||||||
|
if let Some(dot_loc) = expr_snippet.rfind('.') {
|
||||||
|
found = true;
|
||||||
|
err.span_suggestion(
|
||||||
|
expr.span.with_hi(expr.span.lo() + BytePos::from_usize(dot_loc)),
|
||||||
|
"to access the field, dereference first",
|
||||||
|
format!("(*{})", &expr_snippet[0..dot_loc]),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
err.help("methods are immutable and cannot be assigned to");
|
err.help("methods are immutable and cannot be assigned to");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err.emit();
|
err.emit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2330,6 +2330,7 @@ macro_rules! empty_max_mut {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))] // Comparing usize::MAX many elements takes forever in Miri (and in rustc without optimizations)
|
||||||
take_tests! {
|
take_tests! {
|
||||||
slice: &[(); usize::MAX], method: take,
|
slice: &[(); usize::MAX], method: take,
|
||||||
(take_in_bounds_max_range_to, (..usize::MAX), Some(EMPTY_MAX), &[(); 0]),
|
(take_in_bounds_max_range_to, (..usize::MAX), Some(EMPTY_MAX), &[(); 0]),
|
||||||
|
@ -2337,6 +2338,7 @@ take_tests! {
|
||||||
(take_in_bounds_max_range_from, (usize::MAX..), Some(&[] as _), EMPTY_MAX),
|
(take_in_bounds_max_range_from, (usize::MAX..), Some(&[] as _), EMPTY_MAX),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))] // Comparing usize::MAX many elements takes forever in Miri (and in rustc without optimizations)
|
||||||
take_tests! {
|
take_tests! {
|
||||||
slice: &mut [(); usize::MAX], method: take_mut,
|
slice: &mut [(); usize::MAX], method: take_mut,
|
||||||
(take_mut_in_bounds_max_range_to, (..usize::MAX), Some(empty_max_mut!()), &mut [(); 0]),
|
(take_mut_in_bounds_max_range_to, (..usize::MAX), Some(empty_max_mut!()), &mut [(); 0]),
|
||||||
|
|
|
@ -440,12 +440,18 @@ impl Error {
|
||||||
/// `GetLastError` on Windows) and will return a corresponding instance of
|
/// `GetLastError` on Windows) and will return a corresponding instance of
|
||||||
/// [`Error`] for the error code.
|
/// [`Error`] for the error code.
|
||||||
///
|
///
|
||||||
|
/// This should be called immediately after a call to a platform function,
|
||||||
|
/// otherwise the state of the error value is indeterminate. In particular,
|
||||||
|
/// other standard library functions may call platform functions that may
|
||||||
|
/// (or may not) reset the error value even if they succeed.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::io::Error;
|
/// use std::io::Error;
|
||||||
///
|
///
|
||||||
/// println!("last OS error: {:?}", Error::last_os_error());
|
/// let os_error = Error::last_os_error();
|
||||||
|
/// println!("last OS error: {:?}", os_error);
|
||||||
/// ```
|
/// ```
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
@ -12,6 +12,13 @@
|
||||||
//! [`PathBuf`]; note that the paths may differ syntactically by the
|
//! [`PathBuf`]; note that the paths may differ syntactically by the
|
||||||
//! normalization described in the documentation for the [`components`] method.
|
//! normalization described in the documentation for the [`components`] method.
|
||||||
//!
|
//!
|
||||||
|
//! ## Case sensitivity
|
||||||
|
//!
|
||||||
|
//! Unless otherwise indicated path methods that do not access the filesystem,
|
||||||
|
//! such as [`Path::starts_with`] and [`Path::ends_with`], are case sensitive no
|
||||||
|
//! matter the platform or filesystem. An exception to this is made for Windows
|
||||||
|
//! drive letters.
|
||||||
|
//!
|
||||||
//! ## Simple usage
|
//! ## Simple usage
|
||||||
//!
|
//!
|
||||||
//! Path manipulation includes both parsing components from slices and building
|
//! Path manipulation includes both parsing components from slices and building
|
||||||
|
|
|
@ -749,11 +749,42 @@ fn clean_fn_or_proc_macro(
|
||||||
} else {
|
} else {
|
||||||
hir::Constness::NotConst
|
hir::Constness::NotConst
|
||||||
};
|
};
|
||||||
|
clean_fn_decl_legacy_const_generics(&mut func, attrs);
|
||||||
FunctionItem(func)
|
FunctionItem(func)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is needed to make it more "readable" when documenting functions using
|
||||||
|
/// `rustc_legacy_const_generics`. More information in
|
||||||
|
/// <https://github.com/rust-lang/rust/issues/83167>.
|
||||||
|
fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attribute]) {
|
||||||
|
for meta_item_list in attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|a| a.has_name(sym::rustc_legacy_const_generics))
|
||||||
|
.filter_map(|a| a.meta_item_list())
|
||||||
|
{
|
||||||
|
for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.literal()).enumerate() {
|
||||||
|
match literal.kind {
|
||||||
|
ast::LitKind::Int(a, _) => {
|
||||||
|
let gen = func.generics.params.remove(0);
|
||||||
|
if let GenericParamDef { name, kind: GenericParamDefKind::Const { ty, .. } } =
|
||||||
|
gen
|
||||||
|
{
|
||||||
|
func.decl
|
||||||
|
.inputs
|
||||||
|
.values
|
||||||
|
.insert(a as _, Argument { name, type_: *ty, is_const: true });
|
||||||
|
} else {
|
||||||
|
panic!("unexpected non const in position {}", pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("invalid arg index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Clean<Function> for (&'a hir::FnSig<'a>, &'a hir::Generics<'a>, hir::BodyId) {
|
impl<'a> Clean<Function> for (&'a hir::FnSig<'a>, &'a hir::Generics<'a>, hir::BodyId) {
|
||||||
fn clean(&self, cx: &mut DocContext<'_>) -> Function {
|
fn clean(&self, cx: &mut DocContext<'_>) -> Function {
|
||||||
let (generics, decl) = enter_impl_trait(cx, |cx| {
|
let (generics, decl) = enter_impl_trait(cx, |cx| {
|
||||||
|
@ -779,7 +810,7 @@ impl<'a> Clean<Arguments> for (&'a [hir::Ty<'a>], &'a [Ident]) {
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
name = kw::Underscore;
|
name = kw::Underscore;
|
||||||
}
|
}
|
||||||
Argument { name, type_: ty.clean(cx) }
|
Argument { name, type_: ty.clean(cx), is_const: false }
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
@ -798,6 +829,7 @@ impl<'a> Clean<Arguments> for (&'a [hir::Ty<'a>], hir::BodyId) {
|
||||||
.map(|(i, ty)| Argument {
|
.map(|(i, ty)| Argument {
|
||||||
name: name_from_pat(body.params[i].pat),
|
name: name_from_pat(body.params[i].pat),
|
||||||
type_: ty.clean(cx),
|
type_: ty.clean(cx),
|
||||||
|
is_const: false,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
@ -828,6 +860,7 @@ impl<'tcx> Clean<FnDecl> for (DefId, ty::PolyFnSig<'tcx>) {
|
||||||
.map(|t| Argument {
|
.map(|t| Argument {
|
||||||
type_: t.clean(cx),
|
type_: t.clean(cx),
|
||||||
name: names.next().map_or(kw::Empty, |i| i.name),
|
name: names.next().map_or(kw::Empty, |i| i.name),
|
||||||
|
is_const: false,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1353,6 +1353,9 @@ crate struct Arguments {
|
||||||
crate struct Argument {
|
crate struct Argument {
|
||||||
crate type_: Type,
|
crate type_: Type,
|
||||||
crate name: Symbol,
|
crate name: Symbol,
|
||||||
|
/// This field is used to represent "const" arguments from the `rustc_legacy_const_generics`
|
||||||
|
/// feature. More information in <https://github.com/rust-lang/rust/issues/83167>.
|
||||||
|
crate is_const: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
|
|
@ -1177,6 +1177,10 @@ impl clean::FnDecl {
|
||||||
args.push_str(" <br>");
|
args.push_str(" <br>");
|
||||||
args_plain.push(' ');
|
args_plain.push(' ');
|
||||||
}
|
}
|
||||||
|
if input.is_const {
|
||||||
|
args.push_str("const ");
|
||||||
|
args_plain.push_str("const ");
|
||||||
|
}
|
||||||
if !input.name.is_empty() {
|
if !input.name.is_empty() {
|
||||||
args.push_str(&format!("{}: ", input.name));
|
args.push_str(&format!("{}: ", input.name));
|
||||||
args_plain.push_str(&format!("{}: ", input.name));
|
args_plain.push_str(&format!("{}: ", input.name));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- // MIR for `main` before SimplifyBranches-after-const-prop
|
- // MIR for `main` before SimplifyConstCondition-after-const-prop
|
||||||
+ // MIR for `main` after SimplifyBranches-after-const-prop
|
+ // MIR for `main` after SimplifyConstCondition-after-const-prop
|
||||||
|
|
||||||
fn main() -> () {
|
fn main() -> () {
|
||||||
let mut _0: (); // return place in scope 0 at $DIR/switch_int.rs:6:11: 6:11
|
let mut _0: (); // return place in scope 0 at $DIR/switch_int.rs:6:11: 6:11
|
|
@ -2,7 +2,7 @@
|
||||||
fn foo(_: i32) { }
|
fn foo(_: i32) { }
|
||||||
|
|
||||||
// EMIT_MIR switch_int.main.ConstProp.diff
|
// EMIT_MIR switch_int.main.ConstProp.diff
|
||||||
// EMIT_MIR switch_int.main.SimplifyBranches-after-const-prop.diff
|
// EMIT_MIR switch_int.main.SimplifyConstCondition-after-const-prop.diff
|
||||||
fn main() {
|
fn main() {
|
||||||
match 1 {
|
match 1 {
|
||||||
1 => foo(0),
|
1 => foo(0),
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub enum ViewportPercentageLength {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EMIT_MIR early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff
|
// EMIT_MIR early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff
|
||||||
// EMIT_MIR early_otherwise_branch_68867.try_sum EarlyOtherwiseBranch.before SimplifyBranches-final.after
|
// EMIT_MIR early_otherwise_branch_68867.try_sum EarlyOtherwiseBranch.before SimplifyConstCondition-final.after
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn try_sum(
|
pub extern "C" fn try_sum(
|
||||||
x: &ViewportPercentageLength,
|
x: &ViewportPercentageLength,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- // MIR for `try_sum` before EarlyOtherwiseBranch
|
- // MIR for `try_sum` before EarlyOtherwiseBranch
|
||||||
+ // MIR for `try_sum` after SimplifyBranches-final
|
+ // MIR for `try_sum` after SimplifyConstCondition-final
|
||||||
|
|
||||||
fn try_sum(_1: &ViewportPercentageLength, _2: &ViewportPercentageLength) -> Result<ViewportPercentageLength, ()> {
|
fn try_sum(_1: &ViewportPercentageLength, _2: &ViewportPercentageLength) -> Result<ViewportPercentageLength, ()> {
|
||||||
debug x => _1; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:17:5: 17:6
|
debug x => _1; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:17:5: 17:6
|
|
@ -1,5 +1,5 @@
|
||||||
- // MIR for `main` before SimplifyBranches-after-const-prop
|
- // MIR for `main` before SimplifyConstCondition-after-const-prop
|
||||||
+ // MIR for `main` after SimplifyBranches-after-const-prop
|
+ // MIR for `main` after SimplifyConstCondition-after-const-prop
|
||||||
|
|
||||||
fn main() -> () {
|
fn main() -> () {
|
||||||
let mut _0: (); // return place in scope 0 at $DIR/simplify_if.rs:5:11: 5:11
|
let mut _0: (); // return place in scope 0 at $DIR/simplify_if.rs:5:11: 5:11
|
|
@ -1,7 +1,7 @@
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn noop() {}
|
fn noop() {}
|
||||||
|
|
||||||
// EMIT_MIR simplify_if.main.SimplifyBranches-after-const-prop.diff
|
// EMIT_MIR simplify_if.main.SimplifyConstCondition-after-const-prop.diff
|
||||||
fn main() {
|
fn main() {
|
||||||
if false {
|
if false {
|
||||||
noop();
|
noop();
|
||||||
|
|
16
src/test/rustdoc/legacy-const-generic.rs
Normal file
16
src/test/rustdoc/legacy-const-generic.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![crate_name = "foo"]
|
||||||
|
#![feature(rustc_attrs)]
|
||||||
|
|
||||||
|
// @has 'foo/fn.foo.html'
|
||||||
|
// @has - '//*[@class="rust fn"]' 'fn foo(x: usize, const Y: usize, z: usize) -> [usize; 3]'
|
||||||
|
#[rustc_legacy_const_generics(1)]
|
||||||
|
pub fn foo<const Y: usize>(x: usize, z: usize) -> [usize; 3] {
|
||||||
|
[x, Y, z]
|
||||||
|
}
|
||||||
|
|
||||||
|
// @has 'foo/fn.bar.html'
|
||||||
|
// @has - '//*[@class="rust fn"]' 'fn bar(x: usize, const Y: usize, const Z: usize) -> [usize; 3]'
|
||||||
|
#[rustc_legacy_const_generics(1, 2)]
|
||||||
|
pub fn bar<const Y: usize, const Z: usize>(x: usize) -> [usize; 3] {
|
||||||
|
[x, Y, z]
|
||||||
|
}
|
17
src/test/ui/consts/drop_zst.rs
Normal file
17
src/test/ui/consts/drop_zst.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// check-fail
|
||||||
|
|
||||||
|
#![feature(const_precise_live_drops)]
|
||||||
|
|
||||||
|
struct S;
|
||||||
|
|
||||||
|
impl Drop for S {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("Hello!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn foo() {
|
||||||
|
let s = S; //~ destructor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
9
src/test/ui/consts/drop_zst.stderr
Normal file
9
src/test/ui/consts/drop_zst.stderr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
error[E0493]: destructors cannot be evaluated at compile-time
|
||||||
|
--> $DIR/drop_zst.rs:14:9
|
||||||
|
|
|
||||||
|
LL | let s = S;
|
||||||
|
| ^ constant functions cannot evaluate destructors
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0493`.
|
|
@ -7,7 +7,11 @@ LL | if 5 == {
|
||||||
LL | }
|
LL | }
|
||||||
| ^ expected `{`
|
| ^ expected `{`
|
||||||
|
|
|
|
||||||
= help: maybe you forgot the right operand of the condition?
|
help: maybe you forgot the right operand of the condition?
|
||||||
|
--> $DIR/if-without-block.rs:3:10
|
||||||
|
|
|
||||||
|
LL | if 5 == {
|
||||||
|
| ^^
|
||||||
|
|
||||||
error: aborting due to previous error
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
22
src/test/ui/fn/implied-bounds-unnorm-associated-type-2.rs
Normal file
22
src/test/ui/fn/implied-bounds-unnorm-associated-type-2.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
trait Trait {
|
||||||
|
type Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Trait for T {
|
||||||
|
type Type = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f<'a, 'b>(_: <&'a &'b () as Trait>::Type)
|
||||||
|
where
|
||||||
|
'a: 'a,
|
||||||
|
'b: 'b,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g<'a, 'b>() {
|
||||||
|
f::<'a, 'b>(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
19
src/test/ui/lifetimes/issue-76168-hr-outlives.rs
Normal file
19
src/test/ui/lifetimes/issue-76168-hr-outlives.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// edition:2018
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
#![feature(unboxed_closures)]
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
async fn wrapper<F>(f: F)
|
||||||
|
where for<'a> F: FnOnce<(&'a mut i32,)>,
|
||||||
|
for<'a> <F as FnOnce<(&'a mut i32,)>>::Output: Future<Output=()> + 'a
|
||||||
|
{
|
||||||
|
let mut i = 41;
|
||||||
|
f(&mut i).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_one(i: &mut i32) {
|
||||||
|
*i = *i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
10
src/test/ui/parser/issue-91421.rs
Normal file
10
src/test/ui/parser/issue-91421.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// Regression test for issue #91421.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let value = if true && {
|
||||||
|
//~^ ERROR: this `if` expression has a condition, but no block
|
||||||
|
//~| HELP: maybe you forgot the right operand of the condition?
|
||||||
|
3
|
||||||
|
//~^ ERROR: mismatched types [E0308]
|
||||||
|
} else { 4 };
|
||||||
|
}
|
21
src/test/ui/parser/issue-91421.stderr
Normal file
21
src/test/ui/parser/issue-91421.stderr
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
error: this `if` expression has a condition, but no block
|
||||||
|
--> $DIR/issue-91421.rs:4:17
|
||||||
|
|
|
||||||
|
LL | let value = if true && {
|
||||||
|
| ^^
|
||||||
|
|
|
||||||
|
help: maybe you forgot the right operand of the condition?
|
||||||
|
--> $DIR/issue-91421.rs:4:25
|
||||||
|
|
|
||||||
|
LL | let value = if true && {
|
||||||
|
| ^^
|
||||||
|
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/issue-91421.rs:7:9
|
||||||
|
|
|
||||||
|
LL | 3
|
||||||
|
| ^ expected `bool`, found integer
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0308`.
|
55
src/test/ui/traits/project-modulo-regions.rs
Normal file
55
src/test/ui/traits/project-modulo-regions.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// revisions: with_clause without_clause
|
||||||
|
// Tests that `EvaluatedToOkModuloRegions` from a projection sub-obligation
|
||||||
|
// is correctly propagated
|
||||||
|
|
||||||
|
#![feature(rustc_attrs)]
|
||||||
|
|
||||||
|
trait MyTrait {
|
||||||
|
type Assoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyStruct;
|
||||||
|
|
||||||
|
impl MyTrait for MyStruct {
|
||||||
|
// Evaluating this projection will result in `EvaluatedToOkModuloRegions`
|
||||||
|
// (when `with_clause` is enabled)
|
||||||
|
type Assoc = <Bar as MyTrait>::Assoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
// The `where` clause on this impl will cause us to produce `EvaluatedToOkModuloRegions`
|
||||||
|
// when evaluating a projection involving this impl
|
||||||
|
#[cfg(with_clause)]
|
||||||
|
impl MyTrait for Bar where for<'b> &'b (): 'b {
|
||||||
|
type Assoc = bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This impl tests that the `EvaluatedToOkModuoRegions` result that we get
|
||||||
|
// is really due to the `where` clause on the `with_clause` impl
|
||||||
|
#[cfg(without_clause)]
|
||||||
|
impl MyTrait for Bar {
|
||||||
|
type Assoc = bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The implementation of `#[rustc_evaluate_where_clauses]` doesn't perform
|
||||||
|
// normalization, so we need to place the projection predicate behind a normal
|
||||||
|
// trait predicate
|
||||||
|
struct Helper {}
|
||||||
|
trait HelperTrait {}
|
||||||
|
impl HelperTrait for Helper where <MyStruct as MyTrait>::Assoc: Sized {}
|
||||||
|
|
||||||
|
// Evaluating this 'where' clause will (recursively) end up evaluating
|
||||||
|
// `for<'b> &'b (): 'b`, which will produce `EvaluatedToOkModuloRegions`
|
||||||
|
#[rustc_evaluate_where_clauses]
|
||||||
|
fn test(val: MyStruct) where Helper: HelperTrait {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo(val: MyStruct) {
|
||||||
|
test(val);
|
||||||
|
//[with_clause]~^ ERROR evaluate(Binder(TraitPredicate(<Helper as HelperTrait>, polarity:Positive), [])) = Ok(EvaluatedToOkModuloRegions)
|
||||||
|
//[without_clause]~^^ ERROR evaluate(Binder(TraitPredicate(<Helper as HelperTrait>, polarity:Positive), [])) = Ok(EvaluatedToOk)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
11
src/test/ui/traits/project-modulo-regions.with_clause.stderr
Normal file
11
src/test/ui/traits/project-modulo-regions.with_clause.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error: evaluate(Binder(TraitPredicate(<Helper as HelperTrait>, polarity:Positive), [])) = Ok(EvaluatedToOkModuloRegions)
|
||||||
|
--> $DIR/project-modulo-regions.rs:50:5
|
||||||
|
|
|
||||||
|
LL | fn test(val: MyStruct) where Helper: HelperTrait {
|
||||||
|
| ----------- predicate
|
||||||
|
...
|
||||||
|
LL | test(val);
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
error: evaluate(Binder(TraitPredicate(<Helper as HelperTrait>, polarity:Positive), [])) = Ok(EvaluatedToOk)
|
||||||
|
--> $DIR/project-modulo-regions.rs:50:5
|
||||||
|
|
|
||||||
|
LL | fn test(val: MyStruct) where Helper: HelperTrait {
|
||||||
|
| ----------- predicate
|
||||||
|
...
|
||||||
|
LL | test(val);
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
15
src/test/ui/typeck/issue-91210-ptr-method.fixed
Normal file
15
src/test/ui/typeck/issue-91210-ptr-method.fixed
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Regression test for issue #91210.
|
||||||
|
|
||||||
|
// run-rustfix
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
struct Foo { read: i32 }
|
||||||
|
|
||||||
|
unsafe fn blah(x: *mut Foo) {
|
||||||
|
(*x).read = 4;
|
||||||
|
//~^ ERROR: attempted to take value of method
|
||||||
|
//~| HELP: to access the field, dereference first
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
15
src/test/ui/typeck/issue-91210-ptr-method.rs
Normal file
15
src/test/ui/typeck/issue-91210-ptr-method.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Regression test for issue #91210.
|
||||||
|
|
||||||
|
// run-rustfix
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
struct Foo { read: i32 }
|
||||||
|
|
||||||
|
unsafe fn blah(x: *mut Foo) {
|
||||||
|
x.read = 4;
|
||||||
|
//~^ ERROR: attempted to take value of method
|
||||||
|
//~| HELP: to access the field, dereference first
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
11
src/test/ui/typeck/issue-91210-ptr-method.stderr
Normal file
11
src/test/ui/typeck/issue-91210-ptr-method.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error[E0615]: attempted to take value of method `read` on type `*mut Foo`
|
||||||
|
--> $DIR/issue-91210-ptr-method.rs:10:7
|
||||||
|
|
|
||||||
|
LL | x.read = 4;
|
||||||
|
| - ^^^^ method, not a field
|
||||||
|
| |
|
||||||
|
| help: to access the field, dereference first: `(*x)`
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0615`.
|
Loading…
Add table
Add a link
Reference in a new issue