Skip to content

Commit

Permalink
Auto merge of #16298 - riverbl:exclusive-range-hint, r=Veykril
Browse files Browse the repository at this point in the history
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/rust#37854 (comment)) noting that IntelliJ Rust has this feature.
  • Loading branch information
bors committed Jan 7, 2024
2 parents 1c5fa44 + 3c378b9 commit 6ce3f44
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 17 deletions.
2 changes: 1 addition & 1 deletion crates/hir-def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use smallvec::SmallVec;
use syntax::{
ast::{
self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasLoopBody, HasName,
SlicePatComponents,
RangeItem, SlicePatComponents,
},
AstNode, AstPtr, SyntaxNodePtr,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use hir::Semantics;
use ide_db::RootDatabase;
use syntax::ast::RangeItem;
use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Name, Pat};
use syntax::T;

Expand Down
15 changes: 13 additions & 2 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod fn_lifetime_fn;
mod implicit_static;
mod param_name;
mod implicit_drop;
mod range_exclusive;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayHintsConfig {
Expand All @@ -51,6 +52,7 @@ pub struct InlayHintsConfig {
pub param_names_for_lifetime_elision_hints: bool,
pub hide_named_constructor_hints: bool,
pub hide_closure_initialization_hints: bool,
pub range_exclusive_hints: bool,
pub closure_style: ClosureStyle,
pub max_length: Option<usize>,
pub closing_brace_hints_min_lines: Option<usize>,
Expand Down Expand Up @@ -127,6 +129,7 @@ pub enum InlayKind {
Parameter,
Type,
Drop,
RangeExclusive,
}

#[derive(Debug)]
Expand Down Expand Up @@ -517,13 +520,20 @@ fn hints(
closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
closure_ret::hints(hints, famous_defs, config, file_id, it)
},
ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, config, it),
_ => None,
}
},
ast::Pat(it) => {
binding_mode::hints(hints, sema, config, &it);
if let ast::Pat::IdentPat(it) = it {
bind_pat::hints(hints, famous_defs, config, file_id, &it);
match it {
ast::Pat::IdentPat(it) => {
bind_pat::hints(hints, famous_defs, config, file_id, &it);
}
ast::Pat::RangePat(it) => {
range_exclusive::hints(hints, config, it);
}
_ => {}
}
Some(())
},
Expand Down Expand Up @@ -621,6 +631,7 @@ mod tests {
closing_brace_hints_min_lines: None,
fields_to_resolve: InlayFieldsToResolve::empty(),
implicit_drop_hints: false,
range_exclusive_hints: false,
};
pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
type_hints: true,
Expand Down
121 changes: 121 additions & 0 deletions crates/ide/src/inlay_hints/range_exclusive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Implementation of "range exclusive" inlay hints:
//! ```no_run
//! for i in 0../* < */10 {}
//! if let ../* < */100 = 50 {}
//! ```
use syntax::{ast, SyntaxToken, T};

use crate::{InlayHint, InlayHintsConfig};

pub(super) fn hints(
acc: &mut Vec<InlayHint>,
config: &InlayHintsConfig,
range: impl ast::RangeItem,
) -> Option<()> {
(config.range_exclusive_hints && range.end().is_some())
.then(|| {
range.op_token().filter(|token| token.kind() == T![..]).map(|token| {
acc.push(inlay_hint(token));
})
})
.flatten()
}

fn inlay_hint(token: SyntaxToken) -> InlayHint {
InlayHint {
range: token.text_range(),
position: crate::InlayHintPosition::After,
pad_left: false,
pad_right: false,
kind: crate::InlayKind::RangeExclusive,
label: crate::InlayHintLabel::from("<"),
text_edit: None,
needs_resolve: false,
}
}

#[cfg(test)]
mod tests {
use crate::{
inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
InlayHintsConfig,
};

#[test]
fn range_exclusive_expression_bounded_above_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
let a = 0..10;
//^^<
let b = ..100;
//^^<
let c = (2 - 1)..(7 * 8)
//^^<
}"#,
);
}

#[test]
fn range_exclusive_expression_unbounded_above_no_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
let a = 0..;
let b = ..;
}"#,
);
}

#[test]
fn range_inclusive_expression_no_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
let a = 0..=10;
let b = ..=100;
}"#,
);
}

#[test]
fn range_exclusive_pattern_bounded_above_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
if let 0..10 = 0 {}
//^^<
if let ..100 = 0 {}
//^^<
}"#,
);
}

#[test]
fn range_exclusive_pattern_unbounded_above_no_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
if let 0.. = 0 {}
if let .. = 0 {}
}"#,
);
}

#[test]
fn range_inclusive_pattern_no_hints() {
check_with_config(
InlayHintsConfig { range_exclusive_hints: true, ..DISABLED_CONFIG },
r#"
fn main() {
if let 0..=10 = 0 {}
if let ..=100 = 0 {}
}"#,
);
}
}
1 change: 1 addition & 0 deletions crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ impl StaticIndex<'_> {
closure_capture_hints: false,
closing_brace_hints_min_lines: Some(25),
fields_to_resolve: InlayFieldsToResolve::empty(),
range_exclusive_hints: false,
},
file_id,
None,
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/cli/analysis_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ impl flags::AnalysisStats {
max_length: Some(25),
closing_brace_hints_min_lines: Some(20),
fields_to_resolve: InlayFieldsToResolve::empty(),
range_exclusive_hints: true,
},
file_id,
None,
Expand Down
3 changes: 3 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ config_data! {
/// Whether to show function parameter name inlay hints at the call
/// site.
inlayHints_parameterHints_enable: bool = "true",
/// Whether to show exclusive range inlay hints.
inlayHints_rangeExclusiveHints_enable: bool = "false",
/// Whether to show inlay hints for compiler inserted reborrows.
/// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
Expand Down Expand Up @@ -1464,6 +1466,7 @@ impl Config {
} else {
None
},
range_exclusive_hints: self.data.inlayHints_rangeExclusiveHints_enable,
fields_to_resolve: InlayFieldsToResolve {
resolve_text_edits: client_capability_fields.contains("textEdits"),
resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
Expand Down
10 changes: 10 additions & 0 deletions crates/syntax/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ where
{
}

/// Trait to describe operations common to both `RangeExpr` and `RangePat`.
pub trait RangeItem {
type Bound;

fn start(&self) -> Option<Self::Bound>;
fn end(&self) -> Option<Self::Bound>;
fn op_kind(&self) -> Option<RangeOp>;
fn op_token(&self) -> Option<SyntaxToken>;
}

mod support {
use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken};

Expand Down
24 changes: 15 additions & 9 deletions crates/syntax/src/ast/expr_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::{
SyntaxNode, SyntaxToken, T,
};

use super::RangeItem;

impl ast::HasAttrs for ast::Expr {}

impl ast::Expr {
Expand Down Expand Up @@ -227,30 +229,34 @@ impl ast::RangeExpr {
Some((ix, token, bin_op))
})
}
}

pub fn op_kind(&self) -> Option<RangeOp> {
self.op_details().map(|t| t.2)
}

pub fn op_token(&self) -> Option<SyntaxToken> {
self.op_details().map(|t| t.1)
}
impl RangeItem for ast::RangeExpr {
type Bound = ast::Expr;

pub fn start(&self) -> Option<ast::Expr> {
fn start(&self) -> Option<ast::Expr> {
let op_ix = self.op_details()?.0;
self.syntax()
.children_with_tokens()
.take(op_ix)
.find_map(|it| ast::Expr::cast(it.into_node()?))
}

pub fn end(&self) -> Option<ast::Expr> {
fn end(&self) -> Option<ast::Expr> {
let op_ix = self.op_details()?.0;
self.syntax()
.children_with_tokens()
.skip(op_ix + 1)
.find_map(|it| ast::Expr::cast(it.into_node()?))
}

fn op_token(&self) -> Option<SyntaxToken> {
self.op_details().map(|t| t.1)
}

fn op_kind(&self) -> Option<RangeOp> {
self.op_details().map(|t| t.2)
}
}

impl ast::IndexExpr {
Expand Down
34 changes: 31 additions & 3 deletions crates/syntax/src/ast/node_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
ted, NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
};

use super::{RangeItem, RangeOp};

impl ast::Lifetime {
pub fn text(&self) -> TokenText<'_> {
text_of_first_token(self.syntax())
Expand Down Expand Up @@ -875,22 +877,48 @@ impl ast::Module {
}
}

impl ast::RangePat {
pub fn start(&self) -> Option<ast::Pat> {
impl RangeItem for ast::RangePat {
type Bound = ast::Pat;

fn start(&self) -> Option<ast::Pat> {
self.syntax()
.children_with_tokens()
.take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
.filter_map(|it| it.into_node())
.find_map(ast::Pat::cast)
}

pub fn end(&self) -> Option<ast::Pat> {
fn end(&self) -> Option<ast::Pat> {
self.syntax()
.children_with_tokens()
.skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
.filter_map(|it| it.into_node())
.find_map(ast::Pat::cast)
}

fn op_token(&self) -> Option<SyntaxToken> {
self.syntax().children_with_tokens().find_map(|it| {
let token = it.into_token()?;

match token.kind() {
T![..] => Some(token),
T![..=] => Some(token),
_ => None,
}
})
}

fn op_kind(&self) -> Option<RangeOp> {
self.syntax().children_with_tokens().find_map(|it| {
let token = it.into_token()?;

match token.kind() {
T![..] => Some(RangeOp::Exclusive),
T![..=] => Some(RangeOp::Inclusive),
_ => None,
}
})
}
}

impl ast::TokenTree {
Expand Down
2 changes: 1 addition & 1 deletion crates/syntax/src/ast/prec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Precedence representation.
use crate::{
ast::{self, BinaryOp, Expr, HasArgList},
ast::{self, BinaryOp, Expr, HasArgList, RangeItem},
match_ast, AstNode, SyntaxNode,
};

Expand Down
2 changes: 1 addition & 1 deletion crates/syntax/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustc_dependencies::lexer::unescape::{self, unescape_literal, Mode};

use crate::{
algo,
ast::{self, HasAttrs, HasVisibility, IsString},
ast::{self, HasAttrs, HasVisibility, IsString, RangeItem},
match_ast, AstNode, SyntaxError,
SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
SyntaxNode, SyntaxToken, TextSize, T,
Expand Down
5 changes: 5 additions & 0 deletions docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,11 @@ Maximum length for inlay hints. Set to null to have an unlimited length.
Whether to show function parameter name inlay hints at the call
site.
--
[[rust-analyzer.inlayHints.rangeExclusiveHints.enable]]rust-analyzer.inlayHints.rangeExclusiveHints.enable (default: `false`)::
+
--
Whether to show exclusive range inlay hints.
--
[[rust-analyzer.inlayHints.reborrowHints.enable]]rust-analyzer.inlayHints.reborrowHints.enable (default: `"never"`)::
+
--
Expand Down
5 changes: 5 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.inlayHints.rangeExclusiveHints.enable": {
"markdownDescription": "Whether to show exclusive range inlay hints.",
"default": false,
"type": "boolean"
},
"rust-analyzer.inlayHints.reborrowHints.enable": {
"markdownDescription": "Whether to show inlay hints for compiler inserted reborrows.\nThis setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.",
"default": "never",
Expand Down

0 comments on commit 6ce3f44

Please sign in to comment.