Warn the user when a rename will change the meaning of the program

Specifically, when a rename of a local will change some code that refers it to refer another local, or some code that refer another local to refer to it.

We do it by introducing a dummy edit with an annotation. I'm not a fond of this approach, but I don't think LSP has a better way.
This commit is contained in:
Chayim Refael Friedman 2025-02-02 01:49:04 +02:00
parent bd0289e0e9
commit 62e7d2851b
12 changed files with 509 additions and 59 deletions

View file

@ -3,7 +3,7 @@
//!
//! It can be viewed as a dual for `Change`.
use std::{collections::hash_map::Entry, iter, mem};
use std::{collections::hash_map::Entry, fmt, iter, mem};
use crate::text_edit::{TextEdit, TextEditBuilder};
use crate::{assists::Command, syntax_helpers::tree_diff::diff, SnippetCap};
@ -18,23 +18,33 @@ use syntax::{
AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
};
/// An annotation ID associated with an indel, to describe changes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChangeAnnotationId(u32);
impl fmt::Display for ChangeAnnotationId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone)]
pub struct ChangeAnnotation {
pub label: String,
pub needs_confirmation: bool,
pub description: Option<String>,
}
#[derive(Default, Debug, Clone)]
pub struct SourceChange {
pub source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>,
pub file_system_edits: Vec<FileSystemEdit>,
pub is_snippet: bool,
pub annotations: FxHashMap<ChangeAnnotationId, ChangeAnnotation>,
next_annotation_id: u32,
}
impl SourceChange {
/// Creates a new SourceChange with the given label
/// from the edits.
pub fn from_edits(
source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>,
file_system_edits: Vec<FileSystemEdit>,
) -> Self {
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
}
pub fn from_text_edit(file_id: impl Into<FileId>, edit: TextEdit) -> Self {
SourceChange {
source_file_edits: iter::once((file_id.into(), (edit, None))).collect(),
@ -42,6 +52,13 @@ impl SourceChange {
}
}
pub fn insert_annotation(&mut self, annotation: ChangeAnnotation) -> ChangeAnnotationId {
let id = ChangeAnnotationId(self.next_annotation_id);
self.next_annotation_id += 1;
self.annotations.insert(id, annotation);
id
}
/// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
/// edits for a file if some already exist.
pub fn insert_source_edit(&mut self, file_id: impl Into<FileId>, edit: TextEdit) {
@ -120,7 +137,12 @@ impl From<IntMap<FileId, TextEdit>> for SourceChange {
fn from(source_file_edits: IntMap<FileId, TextEdit>) -> SourceChange {
let source_file_edits =
source_file_edits.into_iter().map(|(file_id, edit)| (file_id, (edit, None))).collect();
SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
SourceChange {
source_file_edits,
file_system_edits: Vec::new(),
is_snippet: false,
..SourceChange::default()
}
}
}
@ -482,6 +504,7 @@ impl From<FileSystemEdit> for SourceChange {
source_file_edits: Default::default(),
file_system_edits: vec![edit],
is_snippet: false,
..SourceChange::default()
}
}
}