Auto merge of #82436 - osa1:issue80258, r=nikomatsakis

Allow calling *const methods on *mut values

This allows `*const` methods to be called on `*mut` values.

TODOs:

- [x] ~~Remove debug logs~~ Done.
- [x] ~~I haven't tested, but I think this currently won't work when the `self` value has type like `&&&&& *mut X` because I don't do any autoderefs when probing. To fix this the new code in `rustc_typeck::check::method::probe` needs to reuse `pick_method` somehow as I think that's the function that autoderefs.~~ This works, because autoderefs are done before calling `pick_core`, in `method_autoderef_steps`, called by `probe_op`.
- [x] ~~I should probably move the new `Pick` to `pick_autorefd_method`. If not, I should move it to its own function.~~ Done.
- [ ] ~~Test this with a `Pick` with `to_ptr = true` and `unsize = true`.~~ I think this case cannot happen, because we don't have any array methods with `*mut [X]` receiver. I should confirm that this is true and document this. I've placed two assertions about this.
- [x] ~~Maybe give `(Mutability, bool)` a name and fields~~ I now have a `to_const_ptr` field in `Pick`.
- [x] ~~Changes in `adjust_self_ty` is quite hacky. The problem is we can't deref a pointer, and even if we don't have an adjustment to get the address of a value, so to go from `*mut` to `*const` we need a special case.~~ There's still a special case for `to_const_ptr`, but I'm not sure if we can avoid this.
- [ ] Figure out how `reached_raw_pointer` stuff is used. I suspect only for error messages.

Fixes #80258
This commit is contained in:
bors 2021-03-13 04:38:39 +00:00
commit f42888c15f
4 changed files with 236 additions and 45 deletions

View File

@ -155,32 +155,46 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> {
let mut target =
self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false));
if let Some(mutbl) = pick.autoref {
let region = self.next_region_var(infer::Autoref(self.span, pick.item));
target = self.tcx.mk_ref(region, ty::TypeAndMut { mutbl, ty: target });
let mutbl = match mutbl {
hir::Mutability::Not => AutoBorrowMutability::Not,
hir::Mutability::Mut => AutoBorrowMutability::Mut {
// Method call receivers are the primary use case
// for two-phase borrows.
allow_two_phase_borrow: AllowTwoPhase::Yes,
},
};
adjustments
.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(region, mutbl)), target });
match &pick.autoref_or_ptr_adjustment {
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, unsize }) => {
let region = self.next_region_var(infer::Autoref(self.span, pick.item));
target = self.tcx.mk_ref(region, ty::TypeAndMut { mutbl: *mutbl, ty: target });
let mutbl = match mutbl {
hir::Mutability::Not => AutoBorrowMutability::Not,
hir::Mutability::Mut => AutoBorrowMutability::Mut {
// Method call receivers are the primary use case
// for two-phase borrows.
allow_two_phase_borrow: AllowTwoPhase::Yes,
},
};
adjustments.push(Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(region, mutbl)),
target,
});
if let Some(unsize_target) = pick.unsize {
target = self
.tcx
.mk_ref(region, ty::TypeAndMut { mutbl: mutbl.into(), ty: unsize_target });
adjustments.push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target });
if let Some(unsize_target) = unsize {
target = self
.tcx
.mk_ref(region, ty::TypeAndMut { mutbl: mutbl.into(), ty: unsize_target });
adjustments
.push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target });
}
}
} else {
// No unsizing should be performed without autoref (at
// least during method dispach). This is because we
// currently only unsize `[T;N]` to `[T]`, and naturally
// that must occur being a reference.
assert!(pick.unsize.is_none());
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) => {
target = match target.kind() {
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) => {
assert_eq!(*mutbl, hir::Mutability::Mut);
self.tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty })
}
other => panic!("Cannot adjust receiver type {:?} to const ptr", other),
};
adjustments.push(Adjustment {
kind: Adjust::Pointer(PointerCast::MutToConstPointer),
target,
});
}
None => {}
}
self.register_predicates(autoderef.into_obligations());

View File

@ -154,6 +154,42 @@ enum ProbeResult {
Match,
}
/// When adjusting a receiver we often want to do one of
///
/// - Add a `&` (or `&mut`), converting the recevier from `T` to `&T` (or `&mut T`)
/// - If the receiver has type `*mut T`, convert it to `*const T`
///
/// This type tells us which one to do.
///
/// Note that in principle we could do both at the same time. For example, when the receiver has
/// type `T`, we could autoref it to `&T`, then convert to `*const T`. Or, when it has type `*mut
/// T`, we could convert it to `*const T`, then autoref to `&*const T`. However, currently we do
/// (at most) one of these. Either the receiver has type `T` and we convert it to `&T` (or with
/// `mut`), or it has type `*mut T` and we convert it to `*const T`.
#[derive(Debug, PartialEq, Clone)]
pub enum AutorefOrPtrAdjustment<'tcx> {
/// Receiver has type `T`, add `&` or `&mut` (it `T` is `mut`), and maybe also "unsize" it.
/// Unsizing is used to convert a `[T; N]` to `[T]`, which only makes sense when autorefing.
Autoref {
mutbl: hir::Mutability,
/// Indicates that the source expression should be "unsized" to a target type. This should
/// probably eventually go away in favor of just coercing method receivers.
unsize: Option<Ty<'tcx>>,
},
/// Receiver has type `*mut T`, convert to `*const T`
ToConstPtr,
}
impl<'tcx> AutorefOrPtrAdjustment<'tcx> {
fn get_unsize(&self) -> Option<Ty<'tcx>> {
match self {
AutorefOrPtrAdjustment::Autoref { mutbl: _, unsize } => unsize.clone(),
AutorefOrPtrAdjustment::ToConstPtr => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Pick<'tcx> {
pub item: ty::AssocItem,
@ -165,17 +201,9 @@ pub struct Pick<'tcx> {
/// A = expr | *expr | **expr | ...
pub autoderefs: usize,
/// Indicates that an autoref is applied after the optional autoderefs
///
/// B = A | &A | &mut A
pub autoref: Option<hir::Mutability>,
/// Indicates that the source expression should be "unsized" to a
/// target type. This should probably eventually go away in favor
/// of just coercing method receivers.
///
/// C = B | unsize(B)
pub unsize: Option<Ty<'tcx>>,
/// Indicates that we want to add an autoref (and maybe also unsize it), or if the receiver is
/// `*mut T`, convert it to `*const T`.
pub autoref_or_ptr_adjustment: Option<AutorefOrPtrAdjustment<'tcx>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -714,6 +742,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
debug!("assemble_inherent_impl_probe {:?}", impl_def_id);
let (impl_ty, impl_substs) = self.impl_ty_and_substs(impl_def_id);
let impl_ty = impl_ty.subst(self.tcx, impl_substs);
for item in self.impl_or_trait_item(impl_def_id) {
if !self.has_applicable_self(&item) {
// No receiver declared. Not a candidate.
@ -721,9 +752,6 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
continue;
}
let (impl_ty, impl_substs) = self.impl_ty_and_substs(impl_def_id);
let impl_ty = impl_ty.subst(self.tcx, impl_substs);
// Determine the receiver type that the method itself expects.
let xform_tys = self.xform_self_ty(&item, impl_ty, impl_substs);
@ -1086,6 +1114,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
self.pick_by_value_method(step, self_ty).or_else(|| {
self.pick_autorefd_method(step, self_ty, hir::Mutability::Not)
.or_else(|| self.pick_autorefd_method(step, self_ty, hir::Mutability::Mut))
.or_else(|| self.pick_const_ptr_method(step, self_ty))
})
})
.next()
@ -1113,7 +1142,10 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
// Insert a `&*` or `&mut *` if this is a reference type:
if let ty::Ref(_, _, mutbl) = *step.self_ty.value.value.kind() {
pick.autoderefs += 1;
pick.autoref = Some(mutbl);
pick.autoref_or_ptr_adjustment = Some(AutorefOrPtrAdjustment::Autoref {
mutbl,
unsize: pick.autoref_or_ptr_adjustment.and_then(|a| a.get_unsize()),
})
}
pick
@ -1136,8 +1168,39 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
self.pick_method(autoref_ty).map(|r| {
r.map(|mut pick| {
pick.autoderefs = step.autoderefs;
pick.autoref = Some(mutbl);
pick.unsize = step.unsize.then_some(self_ty);
pick.autoref_or_ptr_adjustment = Some(AutorefOrPtrAdjustment::Autoref {
mutbl,
unsize: step.unsize.then_some(self_ty),
});
pick
})
})
}
/// If `self_ty` is `*mut T` then this picks `*const T` methods. The reason why we have a
/// special case for this is because going from `*mut T` to `*const T` with autoderefs and
/// autorefs would require dereferencing the pointer, which is not safe.
fn pick_const_ptr_method(
&mut self,
step: &CandidateStep<'tcx>,
self_ty: Ty<'tcx>,
) -> Option<PickResult<'tcx>> {
// Don't convert an unsized reference to ptr
if step.unsize {
return None;
}
let ty = match self_ty.kind() {
ty::RawPtr(ty::TypeAndMut { ty, mutbl: hir::Mutability::Mut }) => ty,
_ => return None,
};
let const_self_ty = ty::TypeAndMut { ty, mutbl: hir::Mutability::Not };
let const_ptr_ty = self.tcx.mk_ptr(const_self_ty);
self.pick_method(const_ptr_ty).map(|r| {
r.map(|mut pick| {
pick.autoderefs = step.autoderefs;
pick.autoref_or_ptr_adjustment = Some(AutorefOrPtrAdjustment::ToConstPtr);
pick
})
})
@ -1510,8 +1573,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
kind: TraitPick,
import_ids: probes[0].0.import_ids.clone(),
autoderefs: 0,
autoref: None,
unsize: None,
autoref_or_ptr_adjustment: None,
})
}
@ -1748,8 +1810,7 @@ impl<'tcx> Candidate<'tcx> {
},
import_ids: self.import_ids.clone(),
autoderefs: 0,
autoref: None,
unsize: None,
autoref_or_ptr_adjustment: None,
}
}
}

View File

@ -0,0 +1,20 @@
// EMIT_MIR receiver_ptr_mutability.main.mir_map.0.mir
#![feature(arbitrary_self_types)]
struct Test {}
impl Test {
fn x(self: *const Self) {
println!("x called");
}
}
fn main() {
let ptr: *mut Test = std::ptr::null_mut();
ptr.x();
// Test autoderefs
let ptr_ref: &&&&*mut Test = &&&&ptr;
ptr_ref.x();
}

View File

@ -0,0 +1,96 @@
// MIR for `main` 0 mir_map
| User Type Annotations
| 0: Canonical { max_universe: U0, variables: [], value: Ty(*mut Test) } at $DIR/receiver-ptr-mutability.rs:14:14: 14:23
| 1: Canonical { max_universe: U0, variables: [], value: Ty(*mut Test) } at $DIR/receiver-ptr-mutability.rs:14:14: 14:23
| 2: Canonical { max_universe: U0, variables: [CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }], value: Ty(&&&&*mut Test) } at $DIR/receiver-ptr-mutability.rs:18:18: 18:31
| 3: Canonical { max_universe: U0, variables: [CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }, CanonicalVarInfo { kind: Region(U0) }], value: Ty(&&&&*mut Test) } at $DIR/receiver-ptr-mutability.rs:18:18: 18:31
|
fn main() -> () {
let mut _0: (); // return place in scope 0 at $DIR/receiver-ptr-mutability.rs:13:11: 13:11
let _1: *mut Test as UserTypeProjection { base: UserType(0), projs: [] }; // in scope 0 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
let _2: (); // in scope 0 at $DIR/receiver-ptr-mutability.rs:15:5: 15:12
let mut _3: *const Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
let mut _4: *mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
let _6: &&&&*mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
let _7: &&&*mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:18:35: 18:41
let _8: &&*mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:18:36: 18:41
let _9: &*mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:18:37: 18:41
let _10: (); // in scope 0 at $DIR/receiver-ptr-mutability.rs:19:5: 19:16
let mut _11: *const Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
let mut _12: *mut Test; // in scope 0 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
scope 1 {
debug ptr => _1; // in scope 1 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
let _5: &&&&*mut Test as UserTypeProjection { base: UserType(2), projs: [] }; // in scope 1 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
scope 2 {
debug ptr_ref => _5; // in scope 2 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
}
}
bb0: {
StorageLive(_1); // scope 0 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
_1 = null_mut::<Test>() -> [return: bb1, unwind: bb4]; // scope 0 at $DIR/receiver-ptr-mutability.rs:14:26: 14:46
// mir::Constant
// + span: $DIR/receiver-ptr-mutability.rs:14:26: 14:44
// + literal: Const { ty: fn() -> *mut Test {std::ptr::null_mut::<Test>}, val: Value(Scalar(<ZST>)) }
}
bb1: {
FakeRead(ForLet, _1); // scope 0 at $DIR/receiver-ptr-mutability.rs:14:9: 14:12
AscribeUserType(_1, o, UserTypeProjection { base: UserType(1), projs: [] }); // scope 0 at $DIR/receiver-ptr-mutability.rs:14:14: 14:23
StorageLive(_2); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:12
StorageLive(_3); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
StorageLive(_4); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
_4 = _1; // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
_3 = move _4 as *const Test (Pointer(MutToConstPointer)); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:8
StorageDead(_4); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:7: 15:8
_2 = Test::x(move _3) -> [return: bb2, unwind: bb4]; // scope 1 at $DIR/receiver-ptr-mutability.rs:15:5: 15:12
// mir::Constant
// + span: $DIR/receiver-ptr-mutability.rs:15:9: 15:10
// + literal: Const { ty: fn(*const Test) {Test::x}, val: Value(Scalar(<ZST>)) }
}
bb2: {
StorageDead(_3); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:11: 15:12
StorageDead(_2); // scope 1 at $DIR/receiver-ptr-mutability.rs:15:12: 15:13
StorageLive(_5); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
StorageLive(_6); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
StorageLive(_7); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:35: 18:41
StorageLive(_8); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:36: 18:41
StorageLive(_9); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:37: 18:41
_9 = &_1; // scope 1 at $DIR/receiver-ptr-mutability.rs:18:37: 18:41
_8 = &_9; // scope 1 at $DIR/receiver-ptr-mutability.rs:18:36: 18:41
_7 = &_8; // scope 1 at $DIR/receiver-ptr-mutability.rs:18:35: 18:41
_6 = &_7; // scope 1 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
_5 = &(*_6); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:34: 18:41
FakeRead(ForLet, _5); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:9: 18:16
AscribeUserType(_5, o, UserTypeProjection { base: UserType(3), projs: [] }); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:18: 18:31
StorageDead(_6); // scope 1 at $DIR/receiver-ptr-mutability.rs:18:41: 18:42
StorageLive(_10); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:16
StorageLive(_11); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
StorageLive(_12); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
_12 = (*(*(*(*_5)))); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
_11 = move _12 as *const Test (Pointer(MutToConstPointer)); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:12
StorageDead(_12); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:11: 19:12
_10 = Test::x(move _11) -> [return: bb3, unwind: bb4]; // scope 2 at $DIR/receiver-ptr-mutability.rs:19:5: 19:16
// mir::Constant
// + span: $DIR/receiver-ptr-mutability.rs:19:13: 19:14
// + literal: Const { ty: fn(*const Test) {Test::x}, val: Value(Scalar(<ZST>)) }
}
bb3: {
StorageDead(_11); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:15: 19:16
StorageDead(_10); // scope 2 at $DIR/receiver-ptr-mutability.rs:19:16: 19:17
_0 = const (); // scope 0 at $DIR/receiver-ptr-mutability.rs:13:11: 20:2
StorageDead(_9); // scope 1 at $DIR/receiver-ptr-mutability.rs:20:1: 20:2
StorageDead(_8); // scope 1 at $DIR/receiver-ptr-mutability.rs:20:1: 20:2
StorageDead(_7); // scope 1 at $DIR/receiver-ptr-mutability.rs:20:1: 20:2
StorageDead(_5); // scope 1 at $DIR/receiver-ptr-mutability.rs:20:1: 20:2
StorageDead(_1); // scope 0 at $DIR/receiver-ptr-mutability.rs:20:1: 20:2
return; // scope 0 at $DIR/receiver-ptr-mutability.rs:20:2: 20:2
}
bb4 (cleanup): {
resume; // scope 0 at $DIR/receiver-ptr-mutability.rs:13:1: 20:2
}
}