Skip to content

Rollup of 4 pull requests #140702

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 101 additions & 39 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,20 @@ enum PeelKind {
/// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
/// any number of `&`/`&mut` references, plus a single smart pointer.
ExplicitDerefPat,
/// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
/// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
/// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
/// don't peel it. See [`ResolvedPat`] for more information.
Implicit { until_adt: Option<DefId> },
/// Implicitly peel references, and if `deref_patterns` is enabled, smart pointer ADTs.
Implicit {
/// The ADT the pattern is a constructor for, if applicable, so that we don't peel it. See
/// [`ResolvedPat`] for more information.
until_adt: Option<DefId>,
/// The number of references at the head of the pattern's type, so we can leave that many
/// untouched. This is `1` for string literals, and `0` for most patterns.
pat_ref_layers: usize,
},
}

impl AdjustMode {
const fn peel_until_adt(opt_adt_def: Option<DefId>) -> AdjustMode {
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: opt_adt_def } }
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: opt_adt_def, pat_ref_layers: 0 } }
}
const fn peel_all() -> AdjustMode {
AdjustMode::peel_until_adt(None)
Expand Down Expand Up @@ -488,9 +492,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
match pat.kind {
// Peel off a `&` or `&mut` from the scrutinee type. See the examples in
// `tests/ui/rfcs/rfc-2005-default-binding-mode`.
_ if let AdjustMode::Peel { .. } = adjust_mode
_ if let AdjustMode::Peel { kind: peel_kind } = adjust_mode
&& pat.default_binding_modes
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() =>
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind()
&& self.should_peel_ref(peel_kind, expected) =>
{
debug!("inspecting {:?}", expected);

Expand Down Expand Up @@ -531,24 +536,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
// examples in `tests/ui/pattern/deref_patterns/`.
_ if self.tcx.features().deref_patterns()
&& let AdjustMode::Peel { kind: PeelKind::Implicit { until_adt } } = adjust_mode
&& let AdjustMode::Peel { kind: peel_kind } = adjust_mode
&& pat.default_binding_modes
// For simplicity, only apply overloaded derefs if `expected` is a known ADT.
// FIXME(deref_patterns): we'll get better diagnostics for users trying to
// implicitly deref generics if we allow them here, but primitives, tuples, and
// inference vars definitely should be stopped. Figure out what makes most sense.
&& let ty::Adt(scrutinee_adt, _) = *expected.kind()
// Don't peel if the pattern type already matches the scrutinee. E.g., stop here if
// matching on a `Cow<'a, T>` scrutinee with a `Cow::Owned(_)` pattern.
&& until_adt != Some(scrutinee_adt.did())
// At this point, the pattern isn't able to match `expected` without peeling. Check
// that it implements `Deref` before assuming it's a smart pointer, to get a normal
// type error instead of a missing impl error if not. This only checks for `Deref`,
// not `DerefPure`: we require that too, but we want a trait error if it's missing.
&& let Some(deref_trait) = self.tcx.lang_items().deref_trait()
&& self
.type_implements_trait(deref_trait, [expected], self.param_env)
.may_apply() =>
&& self.should_peel_smart_pointer(peel_kind, expected) =>
{
debug!("scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref");
// The scrutinee is a smart pointer; implicitly dereference it. This adds a
Expand Down Expand Up @@ -680,21 +670,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// String and byte-string literals result in types `&str` and `&[u8]` respectively.
// All other literals result in non-reference types.
// As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}`.
//
// Call `resolve_vars_if_possible` here for inline const blocks.
PatKind::Expr(lt) => match self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt)).kind() {
ty::Ref(..) => AdjustMode::Pass,
_ => {
// Path patterns have already been handled, and inline const blocks currently
// aren't possible to write, so any handling for them would be untested.
if cfg!(debug_assertions)
&& self.tcx.features().deref_patterns()
&& !matches!(lt.kind, PatExprKind::Lit { .. })
{
span_bug!(lt.span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}", lt.kind);
// As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}` unless
// `deref_patterns` is enabled.
PatKind::Expr(lt) => {
// Path patterns have already been handled, and inline const blocks currently
// aren't possible to write, so any handling for them would be untested.
if cfg!(debug_assertions)
&& self.tcx.features().deref_patterns()
&& !matches!(lt.kind, PatExprKind::Lit { .. })
{
span_bug!(lt.span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}", lt.kind);
}
// Call `resolve_vars_if_possible` here for inline const blocks.
let lit_ty = self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt));
// If `deref_patterns` is enabled, allow `if let "foo" = &&"foo" {}`.
if self.tcx.features().deref_patterns() {
let mut peeled_ty = lit_ty;
let mut pat_ref_layers = 0;
while let ty::Ref(_, inner_ty, mutbl) = *peeled_ty.kind() {
// We rely on references at the head of constants being immutable.
debug_assert!(mutbl.is_not());
pat_ref_layers += 1;
peeled_ty = inner_ty;
}
AdjustMode::peel_all()
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: None, pat_ref_layers } }
} else {
if lit_ty.is_ref() { AdjustMode::Pass } else { AdjustMode::peel_all() }
}
},

Expand All @@ -720,6 +721,67 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

/// Assuming `expected` is a reference type, determine whether to peel it before matching.
fn should_peel_ref(&self, peel_kind: PeelKind, mut expected: Ty<'tcx>) -> bool {
debug_assert!(expected.is_ref());
let pat_ref_layers = match peel_kind {
PeelKind::ExplicitDerefPat => 0,
PeelKind::Implicit { pat_ref_layers, .. } => pat_ref_layers,
};

// Most patterns don't have reference types, so we'll want to peel all references from the
// scrutinee before matching. To optimize for the common case, return early.
if pat_ref_layers == 0 {
return true;
}
debug_assert!(
self.tcx.features().deref_patterns(),
"Peeling for patterns with reference types is gated by `deref_patterns`."
);

// If the pattern has as many or more layers of reference as the expected type, we can match
// without peeling more, unless we find a smart pointer or `&mut` that we also need to peel.
// We don't treat `&` and `&mut` as interchangeable, but by peeling `&mut`s before matching,
// we can still, e.g., match on a `&mut str` with a string literal pattern. This is because
// string literal patterns may be used where `str` is expected.
let mut expected_ref_layers = 0;
while let ty::Ref(_, inner_ty, mutbl) = *expected.kind() {
if mutbl.is_mut() {
// Mutable references can't be in the final value of constants, thus they can't be
// at the head of their types, thus we should always peel `&mut`.
return true;
}
expected_ref_layers += 1;
expected = inner_ty;
}
pat_ref_layers < expected_ref_layers || self.should_peel_smart_pointer(peel_kind, expected)
}

/// Determine whether `expected` is a smart pointer type that should be peeled before matching.
fn should_peel_smart_pointer(&self, peel_kind: PeelKind, expected: Ty<'tcx>) -> bool {
// Explicit `deref!(_)` patterns match against smart pointers; don't peel in that case.
if let PeelKind::Implicit { until_adt, .. } = peel_kind
// For simplicity, only apply overloaded derefs if `expected` is a known ADT.
// FIXME(deref_patterns): we'll get better diagnostics for users trying to
// implicitly deref generics if we allow them here, but primitives, tuples, and
// inference vars definitely should be stopped. Figure out what makes most sense.
&& let ty::Adt(scrutinee_adt, _) = *expected.kind()
// Don't peel if the pattern type already matches the scrutinee. E.g., stop here if
// matching on a `Cow<'a, T>` scrutinee with a `Cow::Owned(_)` pattern.
&& until_adt != Some(scrutinee_adt.did())
// At this point, the pattern isn't able to match `expected` without peeling. Check
// that it implements `Deref` before assuming it's a smart pointer, to get a normal
// type error instead of a missing impl error if not. This only checks for `Deref`,
// not `DerefPure`: we require that too, but we want a trait error if it's missing.
&& let Some(deref_trait) = self.tcx.lang_items().deref_trait()
&& self.type_implements_trait(deref_trait, [expected], self.param_env).may_apply()
{
true
} else {
false
}
}

fn check_pat_expr_unadjusted(&self, lt: &'tcx hir::PatExpr<'tcx>) -> Ty<'tcx> {
let ty = match &lt.kind {
rustc_hir::PatExprKind::Lit { lit, negated } => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.20.3
0.20.6
31 changes: 24 additions & 7 deletions src/doc/unstable-book/src/language-features/deref-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,26 @@ let deref!(x) = Box::new(NoCopy) else { unreachable!() };
drop::<NoCopy>(x);
```

Additionally, when `deref_patterns` is enabled, string literal patterns may be written where `str`
is expected. Likewise, byte string literal patterns may be written where `[u8]` or `[u8; _]` is
expected. This lets them be used in `deref!(_)` patterns:
Additionally, `deref_patterns` implements changes to string and byte string literal patterns,
allowing then to be used in deref patterns:

```rust
# #![feature(deref_patterns)]
# #![allow(incomplete_features)]
match ("test".to_string(), b"test".to_vec()) {
(deref!("test"), deref!(b"test")) => {}
match ("test".to_string(), Box::from("test"), b"test".to_vec()) {
("test", "test", b"test") => {}
_ => panic!(),
}

// This works through multiple layers of reference and smart pointer:
match (&Box::new(&"test".to_string()), &&&"test") {
("test", "test") => {}
_ => panic!(),
}

// `deref!("...")` syntax may also be used:
match "test".to_string() {
deref!("test") => {}
_ => panic!(),
}

Expand All @@ -82,10 +93,16 @@ match *"test" {
"test" => {}
_ => panic!(),
}
match *b"test" {
b"test" => {}
_ => panic!(),
}
match *(b"test" as &[u8]) {
b"test" => {}
_ => panic!(),
}
```

Implicit deref pattern syntax is not yet supported for string or byte string literals.

[`box_patterns`]: ./box-patterns.md
[`string_deref_patterns`]: ./string-deref-patterns.md
[smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors
43 changes: 22 additions & 21 deletions src/librustdoc/html/static/css/rustdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\
--collapse-arrow-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 16 16" \
enable-background="new 0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="none" \
d="M3,8l4,4l4,-4m-4,4M3,4l4,4l4,-4" stroke="black" stroke-width="2"/></svg>');
--hamburger-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
viewBox="0 0 22 22" fill="none" stroke="black">\
<path d="M3,5h16M3,11h16M3,17h16" stroke-width="2.75"/></svg>');
}

:root.sans-serif {
Expand Down Expand Up @@ -2001,9 +2004,11 @@ a.tooltip:hover::after {
display: flex;
margin-right: 4px;
position: fixed;
left: 6px;
height: 34px;
width: 34px;
}
.hide-sidebar #sidebar-button {
left: 6px;
background-color: var(--main-background-color);
z-index: 1;
}
Expand All @@ -2019,6 +2024,8 @@ a.tooltip:hover::after {
align-items: center;
justify-content: center;
flex-direction: column;
}
#settings-menu > a, #help-button > a, button#toggle-all-docs {
border: 1px solid transparent;
border-radius: var(--button-border-radius);
color: var(--main-color);
Expand All @@ -2031,14 +2038,15 @@ a.tooltip:hover::after {
min-width: 0;
}
#sidebar-button > a {
background-color: var(--button-background-color);
border-color: var(--border-color);
background-color: var(--sidebar-background-color);
width: 33px;
}
#sidebar-button > a:hover, #sidebar-button > a:focus-visible {
background-color: var(--main-background-color);
}

#settings-menu > a:hover, #settings-menu > a:focus-visible,
#help-button > a:hover, #help-button > a:focus-visible,
#sidebar-button > a:hover, #sidebar-button > a:focus-visible,
button#toggle-all-docs:hover, button#toggle-all-docs:focus-visible {
border-color: var(--settings-button-border-focus);
text-decoration: none;
Expand Down Expand Up @@ -2405,28 +2413,16 @@ However, it's not needed with smaller screen width because the doc/code block is
use hamburger button */
.src #sidebar-button > a::before, .sidebar-menu-toggle::before {
/* hamburger button image */
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
viewBox="0 0 22 22" fill="none" stroke="black">\
<path d="M3,5h16M3,11h16M3,17h16" stroke-width="2.75"/></svg>');
content: var(--hamburger-image);
opacity: 0.75;
filter: var(--mobile-sidebar-menu-filter);
}
.sidebar-menu-toggle:hover::before,
.sidebar-menu-toggle:active::before,
.sidebar-menu-toggle:focus::before {
opacity: 1;
}

/* src sidebar button opens a folder view */
.src #sidebar-button > a::before {
/* folder image */
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
viewBox="0 0 22 22" fill="none" stroke="black">\
<path d="M16,9v-4h-6v-1l-2,-2h-4l-2,2v16h13L21,9h-15L2,19" stroke-width="1.25"/>\
<path d="M15,7h-11v3" stroke-width="0.75"/>\
<path d="M3.75,10v1.25" stroke-width="0.375"/></svg>');
opacity: 0.75;
}

/* Media Queries */

/* Make sure all the buttons line wrap at the same time */
Expand Down Expand Up @@ -2611,9 +2607,6 @@ in src-script.js and main.js
width: 22px;
height: 22px;
}
.sidebar-menu-toggle::before {
filter: var(--mobile-sidebar-menu-filter);
}
.sidebar-menu-toggle:hover {
background: var(--main-background-color);
}
Expand Down Expand Up @@ -2671,6 +2664,14 @@ in src-script.js and main.js
margin: 0 0 -25px 0;
padding: var(--nav-sub-mobile-padding);
}

html:not(.src-sidebar-expanded) .src #sidebar-button > a {
background-color: var(--main-background-color);
}
html:not(.src-sidebar-expanded) .src #sidebar-button > a:hover,
html:not(.src-sidebar-expanded) .src #sidebar-button > a:focus-visible {
background-color: var(--sidebar-background-color);
}
}


Expand Down
36 changes: 36 additions & 0 deletions tests/rustdoc-gui/sidebar.goml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,42 @@ assert-position-false: (".sidebar-crate > h2 > a", {"x": -3})
drag-and-drop: ((205, 100), (108, 100))
assert-position: (".sidebar-crate > h2 > a", {"x": -3})

// Check that the mobile sidebar and the source sidebar use the same icon.
store-css: (".mobile-topbar .sidebar-menu-toggle::before", {"content": image_url})
// Then we go to a source page.
click: ".main-heading .src"
assert-css: ("#sidebar-button a::before", {"content": |image_url|})
// Check that hover events work as expected.
store-css: ("#sidebar-button a", {"background-color": sidebar_background})
move-cursor-to: "#sidebar-button a"
store-css: ("#sidebar-button a:hover", {"background-color": sidebar_background_hover})
assert: |sidebar_background| != |sidebar_background_hover|
click: "#sidebar-button a"
wait-for: "html.src-sidebar-expanded"
assert-css: ("#sidebar-button a:hover", {"background-color": |sidebar_background_hover|})
move-cursor-to: "#settings-menu"
assert-css: ("#sidebar-button a:not(:hover)", {"background-color": |sidebar_background|})
// Closing sidebar.
click: "#sidebar-button a"
wait-for: "html:not(.src-sidebar-expanded)"
// Now we check the same when the sidebar button is moved alongside the search.
set-window-size: (500, 500)
store-css: ("#sidebar-button a:hover", {"background-color": not_sidebar_background_hover})
move-cursor-to: "#settings-menu"
store-css: ("#sidebar-button a:not(:hover)", {"background-color": not_sidebar_background})
// The sidebar background is supposed to be the same as the main background.
assert-css: ("body", {"background-color": |not_sidebar_background|})
assert: |not_sidebar_background| != |not_sidebar_background_hover| && |not_sidebar_background| != |sidebar_background|
// The hover background is supposed to be the same as the sidebar background.
assert: |not_sidebar_background_hover| == |sidebar_background|
click: "#sidebar-button a"
wait-for: "html.src-sidebar-expanded"
// And now the background colors are supposed to be the same as the sidebar since the sidebar has
// been open.
assert-css: ("#sidebar-button a:hover", {"background-color": |sidebar_background_hover|})
move-cursor-to: "h2"
assert-css: ("#sidebar-button a:not(:hover)", {"background-color": |sidebar_background|})

// Configuration option to show TOC in sidebar.
set-local-storage: {"rustdoc-hide-toc": "true"}
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
Expand Down
Loading
Loading