Merge pull request #19647 from roife/fix-issue-19646

fix: panics in inlay hints that produce empty text edits for closure return types
This commit is contained in:
Lukas Wirth 2025-04-22 16:27:28 +00:00 committed by GitHub
commit 4e4aee41c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 41 deletions

View file

@ -8,7 +8,7 @@ use hir::{
ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError, ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError,
HirWrite, ModuleDef, ModuleDefId, Semantics, sym, HirWrite, ModuleDef, ModuleDefId, Semantics, sym,
}; };
use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs}; use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder};
use ide_db::{FxHashSet, text_edit::TextEdit}; use ide_db::{FxHashSet, text_edit::TextEdit};
use itertools::Itertools; use itertools::Itertools;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
@ -813,7 +813,8 @@ fn ty_to_text_edit(
config: &InlayHintsConfig, config: &InlayHintsConfig,
node_for_hint: &SyntaxNode, node_for_hint: &SyntaxNode,
ty: &hir::Type, ty: &hir::Type,
offset_to_insert: TextSize, offset_to_insert_ty: TextSize,
additional_edits: &dyn Fn(&mut TextEditBuilder),
prefix: impl Into<String>, prefix: impl Into<String>,
) -> Option<LazyProperty<TextEdit>> { ) -> Option<LazyProperty<TextEdit>> {
// FIXME: Limit the length and bail out on excess somehow? // FIXME: Limit the length and bail out on excess somehow?
@ -822,8 +823,11 @@ fn ty_to_text_edit(
.and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?; .and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
Some(config.lazy_text_edit(|| { Some(config.lazy_text_edit(|| {
let mut builder = TextEdit::builder(); let mut builder = TextEdit::builder();
builder.insert(offset_to_insert, prefix.into()); builder.insert(offset_to_insert_ty, prefix.into());
builder.insert(offset_to_insert, rendered); builder.insert(offset_to_insert_ty, rendered);
additional_edits(&mut builder);
builder.finish() builder.finish()
})) }))
} }

View file

@ -87,6 +87,7 @@ pub(super) fn hints(
.as_ref() .as_ref()
.map_or_else(|| pat.syntax().text_range(), |t| t.text_range()) .map_or_else(|| pat.syntax().text_range(), |t| t.text_range())
.end(), .end(),
&|_| (),
if colon_token.is_some() { "" } else { ": " }, if colon_token.is_some() { "" } else { ": " },
) )
} else { } else {

View file

@ -1,8 +1,8 @@
//! Implementation of "closure return type" inlay hints. //! Implementation of "closure return type" inlay hints.
//! //!
//! Tests live in [`bind_pat`][super::bind_pat] module. //! Tests live in [`bind_pat`][super::bind_pat] module.
use hir::{DisplayTarget, HirDisplay}; use hir::DisplayTarget;
use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; use ide_db::{famous_defs::FamousDefs, text_edit::TextEditBuilder};
use syntax::ast::{self, AstNode}; use syntax::ast::{self, AstNode};
use crate::{ use crate::{
@ -49,44 +49,29 @@ pub(super) fn hints(
if arrow.is_none() { if arrow.is_none() {
label.prepend_str(" -> "); label.prepend_str(" -> ");
} }
let text_edit = if has_block_body {
ty_to_text_edit( let offset_to_insert_ty =
arrow.as_ref().map_or_else(|| param_list.syntax().text_range(), |t| t.text_range()).end();
// Insert braces if necessary
let insert_braces = |builder: &mut TextEditBuilder| {
if !has_block_body {
if let Some(range) = closure.body().map(|b| b.syntax().text_range()) {
builder.insert(range.start(), "{ ".to_owned());
builder.insert(range.end(), " }".to_owned());
}
}
};
let text_edit = ty_to_text_edit(
sema, sema,
config, config,
descended_closure.syntax(), descended_closure.syntax(),
&ty, &ty,
arrow offset_to_insert_ty,
.as_ref() &insert_braces,
.map_or_else(|| param_list.syntax().text_range(), |t| t.text_range())
.end(),
if arrow.is_none() { " -> " } else { "" }, if arrow.is_none() { " -> " } else { "" },
) );
} else {
Some(config.lazy_text_edit(|| {
let body = closure.body();
let body_range = match body {
Some(body) => body.syntax().text_range(),
None => return TextEdit::builder().finish(),
};
let mut builder = TextEdit::builder();
let insert_pos = param_list.syntax().text_range().end();
let rendered = match sema.scope(descended_closure.syntax()).and_then(|scope| {
ty.display_source_code(scope.db, scope.module().into(), false).ok()
}) {
Some(rendered) => rendered,
None => return TextEdit::builder().finish(),
};
let arrow_text = if arrow.is_none() { " -> ".to_owned() } else { "".to_owned() };
builder.insert(insert_pos, arrow_text);
builder.insert(insert_pos, rendered);
builder.insert(body_range.start(), "{ ".to_owned());
builder.insert(body_range.end(), " }".to_owned());
builder.finish()
}))
};
acc.push(InlayHint { acc.push(InlayHint {
range: param_list.syntax().text_range(), range: param_list.syntax().text_range(),