Skip to content

Commit 6ce3f44

Browse files
committed
Auto merge of rust-lang#16298 - riverbl:exclusive-range-hint, r=Veykril
feat: Add inlay hint for exclusive ranges Adds an inlay hint containing a '<' character to exclusive range expressions and patterns that specify an upper bound. ![2024-01-07-095056_257x415_scrot](https://github.com/rust-lang/rust-analyzer/assets/94326797/d6bbc0de-52a5-4af4-b53c-a034749b6cab) Inspired by [this comment](rust-lang#37854 (comment)) noting that IntelliJ Rust has this feature.
2 parents 1c5fa44 + 3c378b9 commit 6ce3f44

File tree

14 files changed

+209
-17
lines changed

14 files changed

+209
-17
lines changed

Diff for: crates/hir-def/src/body/lower.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use smallvec::SmallVec;
1717
use syntax::{
1818
ast::{
1919
self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasLoopBody, HasName,
20-
SlicePatComponents,
20+
RangeItem, SlicePatComponents,
2121
},
2222
AstNode, AstPtr, SyntaxNodePtr,
2323
};

Diff for: crates/ide-assists/src/handlers/convert_let_else_to_match.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use hir::Semantics;
22
use ide_db::RootDatabase;
3+
use syntax::ast::RangeItem;
34
use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Name, Pat};
45
use syntax::T;
56

Diff for: crates/ide/src/inlay_hints.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod fn_lifetime_fn;
3232
mod implicit_static;
3333
mod param_name;
3434
mod implicit_drop;
35+
mod range_exclusive;
3536

3637
#[derive(Clone, Debug, PartialEq, Eq)]
3738
pub struct InlayHintsConfig {
@@ -51,6 +52,7 @@ pub struct InlayHintsConfig {
5152
pub param_names_for_lifetime_elision_hints: bool,
5253
pub hide_named_constructor_hints: bool,
5354
pub hide_closure_initialization_hints: bool,
55+
pub range_exclusive_hints: bool,
5456
pub closure_style: ClosureStyle,
5557
pub max_length: Option<usize>,
5658
pub closing_brace_hints_min_lines: Option<usize>,
@@ -127,6 +129,7 @@ pub enum InlayKind {
127129
Parameter,
128130
Type,
129131
Drop,
132+
RangeExclusive,
130133
}
131134

132135
#[derive(Debug)]
@@ -517,13 +520,20 @@ fn hints(
517520
closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
518521
closure_ret::hints(hints, famous_defs, config, file_id, it)
519522
},
523+
ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, config, it),
520524
_ => None,
521525
}
522526
},
523527
ast::Pat(it) => {
524528
binding_mode::hints(hints, sema, config, &it);
525-
if let ast::Pat::IdentPat(it) = it {
526-
bind_pat::hints(hints, famous_defs, config, file_id, &it);
529+
match it {
530+
ast::Pat::IdentPat(it) => {
531+
bind_pat::hints(hints, famous_defs, config, file_id, &it);
532+
}
533+
ast::Pat::RangePat(it) => {
534+
range_exclusive::hints(hints, config, it);
535+
}
536+
_ => {}
527537
}
528538
Some(())
529539
},
@@ -621,6 +631,7 @@ mod tests {
621631
closing_brace_hints_min_lines: None,
622632
fields_to_resolve: InlayFieldsToResolve::empty(),
623633
implicit_drop_hints: false,
634+
range_exclusive_hints: false,
624635
};
625636
pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
626637
type_hints: true,

Diff for: crates/ide/src/inlay_hints/range_exclusive.rs

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Implementation of "range exclusive" inlay hints:
2+
//! ```no_run
3+
//! for i in 0../* < */10 {}
4+
//! if let ../* < */100 = 50 {}
5+
//! ```
6+
use syntax::{ast, SyntaxToken, T};
7+
8+
use crate::{InlayHint, InlayHintsConfig};
9+
10+
pub(super) fn hints(
11+
acc: &mut Vec<InlayHint>,
12+
config: &InlayHintsConfig,
13+
range: impl ast::RangeItem,
14+
) -> Option<()> {
15+
(config.range_exclusive_hints && range.end().is_some())
16+
.then(|| {
17+
range.op_token().filter(|token| token.kind() == T![..]).map(|token| {
18+
acc.push(inlay_hint(token));
19+
})
20+
})
21+
.flatten()
22+
}
23+
24+
fn inlay_hint(token: SyntaxToken) -> InlayHint {
25+
InlayHint {
26+
range: token.text_range(),
27+
position: crate::InlayHintPosition::After,
28+
pad_left: false,
29+
pad_right: false,
30+
kind: crate::InlayKind::RangeExclusive,
31+
label: crate::InlayHintLabel::from("<"),
32+
text_edit: None,
33+
needs_resolve: false,
34+
}
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use crate::{
40+
inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
41+
InlayHintsConfig,
42+
};
43+
44+
#[test]
45+
fn range_exclusive_expression_bounded_above_hints() {
46+
check_with_config(
47+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
48+
r#"
49+
fn main() {
50+
let a = 0..10;
51+
//^^<
52+
let b = ..100;
53+
//^^<
54+
let c = (2 - 1)..(7 * 8)
55+
//^^<
56+
}"#,
57+
);
58+
}
59+
60+
#[test]
61+
fn range_exclusive_expression_unbounded_above_no_hints() {
62+
check_with_config(
63+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
64+
r#"
65+
fn main() {
66+
let a = 0..;
67+
let b = ..;
68+
}"#,
69+
);
70+
}
71+
72+
#[test]
73+
fn range_inclusive_expression_no_hints() {
74+
check_with_config(
75+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
76+
r#"
77+
fn main() {
78+
let a = 0..=10;
79+
let b = ..=100;
80+
}"#,
81+
);
82+
}
83+
84+
#[test]
85+
fn range_exclusive_pattern_bounded_above_hints() {
86+
check_with_config(
87+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
88+
r#"
89+
fn main() {
90+
if let 0..10 = 0 {}
91+
//^^<
92+
if let ..100 = 0 {}
93+
//^^<
94+
}"#,
95+
);
96+
}
97+
98+
#[test]
99+
fn range_exclusive_pattern_unbounded_above_no_hints() {
100+
check_with_config(
101+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
102+
r#"
103+
fn main() {
104+
if let 0.. = 0 {}
105+
if let .. = 0 {}
106+
}"#,
107+
);
108+
}
109+
110+
#[test]
111+
fn range_inclusive_pattern_no_hints() {
112+
check_with_config(
113+
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
114+
r#"
115+
fn main() {
116+
if let 0..=10 = 0 {}
117+
if let ..=100 = 0 {}
118+
}"#,
119+
);
120+
}
121+
}

Diff for: crates/ide/src/static_index.rs

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ impl StaticIndex<'_> {
133133
closure_capture_hints: false,
134134
closing_brace_hints_min_lines: Some(25),
135135
fields_to_resolve: InlayFieldsToResolve::empty(),
136+
range_exclusive_hints: false,
136137
},
137138
file_id,
138139
None,

Diff for: crates/rust-analyzer/src/cli/analysis_stats.rs

+1
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ impl flags::AnalysisStats {
792792
max_length: Some(25),
793793
closing_brace_hints_min_lines: Some(20),
794794
fields_to_resolve: InlayFieldsToResolve::empty(),
795+
range_exclusive_hints: true,
795796
},
796797
file_id,
797798
None,

Diff for: crates/rust-analyzer/src/config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ config_data! {
399399
/// Whether to show function parameter name inlay hints at the call
400400
/// site.
401401
inlayHints_parameterHints_enable: bool = "true",
402+
/// Whether to show exclusive range inlay hints.
403+
inlayHints_rangeExclusiveHints_enable: bool = "false",
402404
/// Whether to show inlay hints for compiler inserted reborrows.
403405
/// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
404406
inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
@@ -1464,6 +1466,7 @@ impl Config {
14641466
} else {
14651467
None
14661468
},
1469+
range_exclusive_hints: self.data.inlayHints_rangeExclusiveHints_enable,
14671470
fields_to_resolve: InlayFieldsToResolve {
14681471
resolve_text_edits: client_capability_fields.contains("textEdits"),
14691472
resolve_hint_tooltip: client_capability_fields.contains("tooltip"),

Diff for: crates/syntax/src/ast.rs

+10
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ where
136136
{
137137
}
138138

139+
/// Trait to describe operations common to both `RangeExpr` and `RangePat`.
140+
pub trait RangeItem {
141+
type Bound;
142+
143+
fn start(&self) -> Option<Self::Bound>;
144+
fn end(&self) -> Option<Self::Bound>;
145+
fn op_kind(&self) -> Option<RangeOp>;
146+
fn op_token(&self) -> Option<SyntaxToken>;
147+
}
148+
139149
mod support {
140150
use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken};
141151

Diff for: crates/syntax/src/ast/expr_ext.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use crate::{
1313
SyntaxNode, SyntaxToken, T,
1414
};
1515

16+
use super::RangeItem;
17+
1618
impl ast::HasAttrs for ast::Expr {}
1719

1820
impl ast::Expr {
@@ -227,30 +229,34 @@ impl ast::RangeExpr {
227229
Some((ix, token, bin_op))
228230
})
229231
}
232+
}
230233

231-
pub fn op_kind(&self) -> Option<RangeOp> {
232-
self.op_details().map(|t| t.2)
233-
}
234-
235-
pub fn op_token(&self) -> Option<SyntaxToken> {
236-
self.op_details().map(|t| t.1)
237-
}
234+
impl RangeItem for ast::RangeExpr {
235+
type Bound = ast::Expr;
238236

239-
pub fn start(&self) -> Option<ast::Expr> {
237+
fn start(&self) -> Option<ast::Expr> {
240238
let op_ix = self.op_details()?.0;
241239
self.syntax()
242240
.children_with_tokens()
243241
.take(op_ix)
244242
.find_map(|it| ast::Expr::cast(it.into_node()?))
245243
}
246244

247-
pub fn end(&self) -> Option<ast::Expr> {
245+
fn end(&self) -> Option<ast::Expr> {
248246
let op_ix = self.op_details()?.0;
249247
self.syntax()
250248
.children_with_tokens()
251249
.skip(op_ix + 1)
252250
.find_map(|it| ast::Expr::cast(it.into_node()?))
253251
}
252+
253+
fn op_token(&self) -> Option<SyntaxToken> {
254+
self.op_details().map(|t| t.1)
255+
}
256+
257+
fn op_kind(&self) -> Option<RangeOp> {
258+
self.op_details().map(|t| t.2)
259+
}
254260
}
255261

256262
impl ast::IndexExpr {

Diff for: crates/syntax/src/ast/node_ext.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::{
1414
ted, NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
1515
};
1616

17+
use super::{RangeItem, RangeOp};
18+
1719
impl ast::Lifetime {
1820
pub fn text(&self) -> TokenText<'_> {
1921
text_of_first_token(self.syntax())
@@ -875,22 +877,48 @@ impl ast::Module {
875877
}
876878
}
877879

878-
impl ast::RangePat {
879-
pub fn start(&self) -> Option<ast::Pat> {
880+
impl RangeItem for ast::RangePat {
881+
type Bound = ast::Pat;
882+
883+
fn start(&self) -> Option<ast::Pat> {
880884
self.syntax()
881885
.children_with_tokens()
882886
.take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
883887
.filter_map(|it| it.into_node())
884888
.find_map(ast::Pat::cast)
885889
}
886890

887-
pub fn end(&self) -> Option<ast::Pat> {
891+
fn end(&self) -> Option<ast::Pat> {
888892
self.syntax()
889893
.children_with_tokens()
890894
.skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
891895
.filter_map(|it| it.into_node())
892896
.find_map(ast::Pat::cast)
893897
}
898+
899+
fn op_token(&self) -> Option<SyntaxToken> {
900+
self.syntax().children_with_tokens().find_map(|it| {
901+
let token = it.into_token()?;
902+
903+
match token.kind() {
904+
T![..] => Some(token),
905+
T![..=] => Some(token),
906+
_ => None,
907+
}
908+
})
909+
}
910+
911+
fn op_kind(&self) -> Option<RangeOp> {
912+
self.syntax().children_with_tokens().find_map(|it| {
913+
let token = it.into_token()?;
914+
915+
match token.kind() {
916+
T![..] => Some(RangeOp::Exclusive),
917+
T![..=] => Some(RangeOp::Inclusive),
918+
_ => None,
919+
}
920+
})
921+
}
894922
}
895923

896924
impl ast::TokenTree {

Diff for: crates/syntax/src/ast/prec.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Precedence representation.
22
33
use crate::{
4-
ast::{self, BinaryOp, Expr, HasArgList},
4+
ast::{self, BinaryOp, Expr, HasArgList, RangeItem},
55
match_ast, AstNode, SyntaxNode,
66
};
77

Diff for: crates/syntax/src/validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_dependencies::lexer::unescape::{self, unescape_literal, Mode};
99

1010
use crate::{
1111
algo,
12-
ast::{self, HasAttrs, HasVisibility, IsString},
12+
ast::{self, HasAttrs, HasVisibility, IsString, RangeItem},
1313
match_ast, AstNode, SyntaxError,
1414
SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
1515
SyntaxNode, SyntaxToken, TextSize, T,

Diff for: docs/user/generated_config.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,11 @@ Maximum length for inlay hints. Set to null to have an unlimited length.
596596
Whether to show function parameter name inlay hints at the call
597597
site.
598598
--
599+
[[rust-analyzer.inlayHints.rangeExclusiveHints.enable]]rust-analyzer.inlayHints.rangeExclusiveHints.enable (default: `false`)::
600+
+
601+
--
602+
Whether to show exclusive range inlay hints.
603+
--
599604
[[rust-analyzer.inlayHints.reborrowHints.enable]]rust-analyzer.inlayHints.reborrowHints.enable (default: `"never"`)::
600605
+
601606
--

Diff for: editors/code/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,11 @@
13081308
"default": true,
13091309
"type": "boolean"
13101310
},
1311+
"rust-analyzer.inlayHints.rangeExclusiveHints.enable": {
1312+
"markdownDescription": "Whether to show exclusive range inlay hints.",
1313+
"default": false,
1314+
"type": "boolean"
1315+
},
13111316
"rust-analyzer.inlayHints.reborrowHints.enable": {
13121317
"markdownDescription": "Whether to show inlay hints for compiler inserted reborrows.\nThis setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.",
13131318
"default": "never",

0 commit comments

Comments
 (0)