Skip to content

Commit 1716760

Browse files
committed
refactor(ide): model semantic target alternatives
1 parent dadd0fd commit 1716760

10 files changed

Lines changed: 257 additions & 55 deletions

File tree

crates/ide/src/document_highlight.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub(crate) fn document_highlight(
3434
let hir_file_id = file_id.into();
3535
let parsed_file = sema.parse_file(file_id);
3636
let target = resolve_semantic_target(db, file_id, offset, parsed_file.root(), token_precedence);
37-
let SemanticTarget::Source(target) = target.for_intent(TargetIntent::Highlight)? else {
37+
let SemanticTarget::Source(target) = target.unique_for_intent(TargetIntent::Highlight)? else {
3838
return None;
3939
};
4040
let tokens = target.into_tokens();

crates/ide/src/goto_declaration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub(crate) fn goto_declaration(
2424
parsed_file.root(),
2525
goto_definition::token_precedence,
2626
);
27-
let SemanticTarget::Source(target) = target.for_intent(TargetIntent::Navigate)? else {
27+
let SemanticTarget::Source(target) = target.unique_for_intent(TargetIntent::Navigate)? else {
2828
return None;
2929
};
3030
let (range, tokens) = target.into_parts();

crates/ide/src/goto_definition.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,32 @@ fn render_definition_target(
4040
sema: &Semantics<RootDb>,
4141
target: TargetResolution<'_>,
4242
) -> Option<RangeInfo<Vec<NavTarget>>> {
43-
match target.for_intent(TargetIntent::Navigate)? {
44-
SemanticTarget::PreprocMacro(target) => render_preproc_definition_target(target),
45-
SemanticTarget::Include(includes) => render_include_definition_target(db, includes),
46-
SemanticTarget::Source(target) => {
47-
render_source_definition_target(db, file_id, sema, target)
48-
}
43+
let mut ranges = Vec::new();
44+
let mut navs = Vec::new();
45+
for target in target.targets_for_intent(TargetIntent::Navigate) {
46+
let target = match target {
47+
SemanticTarget::PreprocMacro(target) => render_preproc_definition_target(target),
48+
SemanticTarget::Include(includes) => render_include_definition_target(db, includes),
49+
SemanticTarget::Source(target) => {
50+
render_source_definition_target(db, file_id, sema, target)
51+
}
52+
}?;
53+
ranges.push(target.range);
54+
navs.extend(target.info);
55+
}
56+
57+
if navs.is_empty() {
58+
return None;
4959
}
60+
61+
let range = covering_range(&ranges)?;
62+
Some(RangeInfo::new(range, navs.into_iter().unique().collect()))
63+
}
64+
65+
fn covering_range(ranges: &[TextRange]) -> Option<TextRange> {
66+
let start = ranges.iter().map(|range| range.start()).min()?;
67+
let end = ranges.iter().map(|range| range.end()).max()?;
68+
Some(TextRange::new(start, end))
5069
}
5170

5271
fn render_source_definition_target(

crates/ide/src/hover.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,38 @@ fn render_hover_target(
6464
sema: &Semantics<RootDb>,
6565
target: TargetResolution<'_>,
6666
) -> Option<RangeInfo<Markup>> {
67-
match target.for_intent(TargetIntent::Describe)? {
68-
SemanticTarget::PreprocMacro(target) => {
69-
render_macro_hover_target(db, file_id, offset, target)
70-
}
71-
SemanticTarget::Include(includes) => render_include_hover(db, includes),
72-
SemanticTarget::Source(target) => {
73-
let hover = hover_for_source_target(sema, file_id.into(), target)?;
74-
Some(with_expanded_macro_hover(db, file_id, offset, hover))
75-
}
67+
let mut ranges = Vec::new();
68+
let mut markups = Vec::new();
69+
let mut has_source_target = false;
70+
71+
for target in target.targets_for_intent(TargetIntent::Describe) {
72+
let hover = match target {
73+
SemanticTarget::PreprocMacro(target) => {
74+
render_macro_hover_target(db, file_id, offset, target)
75+
}
76+
SemanticTarget::Include(includes) => render_include_hover(db, includes),
77+
SemanticTarget::Source(target) => {
78+
has_source_target = true;
79+
hover_for_source_target(sema, file_id.into(), target)
80+
}
81+
}?;
82+
ranges.push(hover.range);
83+
markups.push(hover.info);
7684
}
85+
86+
let range = covering_range(&ranges)?;
87+
let hover = RangeInfo::new(range, merge_hover_results(markups)?);
88+
Some(if has_source_target {
89+
with_expanded_macro_hover(db, file_id, offset, hover)
90+
} else {
91+
hover
92+
})
93+
}
94+
95+
fn covering_range(ranges: &[TextRange]) -> Option<TextRange> {
96+
let start = ranges.iter().map(|range| range.start()).min()?;
97+
let end = ranges.iter().map(|range| range.end()).max()?;
98+
Some(TextRange::new(start, end))
7799
}
78100

79101
fn hover_for_source_target(

crates/ide/src/references.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn render_references_target(
103103
target: TargetResolution<'_>,
104104
config: ReferencesConfig,
105105
) -> Option<Vec<References>> {
106-
match target.for_intent(TargetIntent::FindReferences)? {
106+
match target.unique_for_intent(TargetIntent::FindReferences)? {
107107
SemanticTarget::PreprocMacro(target) => {
108108
render_preproc_references_target(db, file_id, target, &config)
109109
}

crates/ide/src/rename.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ fn resolve_rename_target(
228228
rename_token_precedence,
229229
);
230230
let SemanticTarget::Source(target) =
231-
target.for_intent(TargetIntent::Rename).ok_or(RenameError::NoRefFound)?
231+
target.unique_for_intent(TargetIntent::Rename).ok_or(RenameError::NoRefFound)?
232232
else {
233233
return Err(RenameError::NoRefFound);
234234
};

crates/ide/src/semantic_target.rs

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use vfs::FileId;
1111
use crate::{
1212
db::root_db::RootDb,
1313
source_targets::{
14-
SourceTarget, SourceTargetBlock, SourceTargetBlockReason, SourceTargetDomain,
15-
SourceTargetResolution, source_target_at_offset,
14+
SourceTarget, SourceTargetAlternatives, SourceTargetAmbiguity, SourceTargetBlock,
15+
SourceTargetBlockReason, SourceTargetDomain, SourceTargetResolution,
16+
source_target_at_offset,
1617
},
1718
};
1819

@@ -51,30 +52,30 @@ bitflags::bitflags! {
5152
#[derive(Debug, Clone)]
5253
pub(crate) enum TargetResolution<'tree> {
5354
Resolved(TargetCandidate<'tree>),
54-
Ambiguous(TargetAmbiguity),
55+
Ambiguous(TargetAlternatives<'tree>),
5556
Blocked(TargetBlock),
5657
Unresolved,
5758
}
5859

5960
impl<'tree> TargetResolution<'tree> {
60-
pub(crate) fn for_intent(self, intent: TargetIntent) -> Option<SemanticTarget<'tree>> {
61-
self.into_primary(intent.capability())
61+
pub(crate) fn unique_for_intent(self, intent: TargetIntent) -> Option<SemanticTarget<'tree>> {
62+
let mut targets = self.targets_for_intent(intent);
63+
(targets.len() == 1).then(|| targets.pop().expect("single target should exist"))
6264
}
6365

64-
fn into_primary(self, required: TargetCapability) -> Option<SemanticTarget<'tree>> {
66+
pub(crate) fn targets_for_intent(self, intent: TargetIntent) -> Vec<SemanticTarget<'tree>> {
67+
let required = intent.capability();
6568
match self {
66-
TargetResolution::Resolved(candidate) => candidate.into_target(required),
67-
TargetResolution::Ambiguous(ambiguity) => {
68-
let TargetAmbiguity { anchor, reason } = ambiguity;
69-
let _ = (anchor, reason);
70-
None
69+
TargetResolution::Resolved(candidate) => {
70+
candidate.into_target(required).into_iter().collect()
7171
}
72+
TargetResolution::Ambiguous(alternatives) => alternatives.into_targets(required),
7273
TargetResolution::Blocked(block) => {
7374
let TargetBlock { anchor, reason } = block;
7475
let _ = (anchor, reason);
75-
None
76+
Vec::new()
7677
}
77-
TargetResolution::Unresolved => None,
78+
TargetResolution::Unresolved => Vec::new(),
7879
}
7980
}
8081

@@ -87,6 +88,9 @@ impl<'tree> TargetResolution<'tree> {
8788
let capabilities = source_capabilities();
8889
Self::Resolved(TargetCandidate::new(SemanticTarget::Source(target), capabilities))
8990
}
91+
SourceTargetResolution::Ambiguous(alternatives) => {
92+
Self::from_source_alternatives(file_id, alternatives)
93+
}
9094
SourceTargetResolution::Blocked(block) => {
9195
let SourceTargetBlock { range, .. } = block.clone();
9296
let anchor = TargetAnchor {
@@ -99,6 +103,22 @@ impl<'tree> TargetResolution<'tree> {
99103
}
100104
}
101105

106+
fn from_source_alternatives(
107+
file_id: FileId,
108+
alternatives: SourceTargetAlternatives<'tree>,
109+
) -> Self {
110+
let SourceTargetAlternatives { domain, range, reason, targets } = alternatives;
111+
let anchor =
112+
TargetAnchor { file_id, range, origin: TargetOrigin::from_source_domain(domain) };
113+
let reason = TargetAmbiguityReason::from_source(reason);
114+
let capabilities = source_capabilities();
115+
let candidates = targets
116+
.into_iter()
117+
.map(|target| TargetCandidate::new(SemanticTarget::Source(target), capabilities))
118+
.collect();
119+
Self::Ambiguous(TargetAlternatives { anchor, reason, candidates })
120+
}
121+
102122
fn from_preproc_macro(target: PreprocMacroTarget) -> Self {
103123
let capabilities = target.capabilities();
104124
Self::Resolved(TargetCandidate::new(SemanticTarget::PreprocMacro(target), capabilities))
@@ -118,9 +138,10 @@ impl<'tree> TargetResolution<'tree> {
118138
Self::Blocked(TargetBlock { anchor, reason: TargetBlockReason::PreprocUnavailable })
119139
}
120140
(SourceTargetDomain::Preproc, SourceTargetBlockReason::Ambiguous { hits }) => {
121-
Self::Ambiguous(TargetAmbiguity {
141+
Self::Ambiguous(TargetAlternatives {
122142
anchor,
123-
reason: TargetAmbiguityReason::PreprocHits { candidate_count: hits.len() },
143+
reason: TargetAmbiguityReason::PreprocHits { hit_count: hits.len() },
144+
candidates: Vec::new(),
124145
})
125146
}
126147
}
@@ -140,11 +161,15 @@ pub(crate) enum TargetOrigin {
140161
}
141162

142163
impl TargetOrigin {
143-
fn from_source_block(block: &SourceTargetBlock) -> Self {
144-
match block.domain {
164+
fn from_source_domain(domain: SourceTargetDomain) -> Self {
165+
match domain {
145166
SourceTargetDomain::Preproc => TargetOrigin::MacroExpansion,
146167
}
147168
}
169+
170+
fn from_source_block(block: &SourceTargetBlock) -> Self {
171+
Self::from_source_domain(block.domain)
172+
}
148173
}
149174

150175
#[derive(Debug, Clone)]
@@ -168,14 +193,33 @@ impl<'tree> TargetCandidate<'tree> {
168193
}
169194

170195
#[derive(Debug, Clone)]
171-
pub(crate) struct TargetAmbiguity {
196+
pub(crate) struct TargetAlternatives<'tree> {
172197
pub anchor: TargetAnchor,
173198
pub reason: TargetAmbiguityReason,
199+
pub candidates: Vec<TargetCandidate<'tree>>,
200+
}
201+
202+
impl<'tree> TargetAlternatives<'tree> {
203+
fn into_targets(self, required: TargetCapability) -> Vec<SemanticTarget<'tree>> {
204+
let Self { anchor, reason, candidates } = self;
205+
let _ = (anchor, reason);
206+
candidates.into_iter().filter_map(|candidate| candidate.into_target(required)).collect()
207+
}
174208
}
175209

176210
#[derive(Debug, Clone, PartialEq, Eq)]
177211
pub(crate) enum TargetAmbiguityReason {
178-
PreprocHits { candidate_count: usize },
212+
PreprocHits { hit_count: usize },
213+
}
214+
215+
impl TargetAmbiguityReason {
216+
fn from_source(reason: SourceTargetAmbiguity) -> Self {
217+
match reason {
218+
SourceTargetAmbiguity::PreprocHits { hit_count } => {
219+
TargetAmbiguityReason::PreprocHits { hit_count }
220+
}
221+
}
222+
}
179223
}
180224

181225
#[derive(Debug, Clone)]
@@ -345,11 +389,11 @@ mod tests {
345389
let resolution =
346390
resolve_semantic_target(host.raw_db(), file_id, offset, Some(root), token_precedence);
347391
assert!(matches!(
348-
resolution.clone().for_intent(TargetIntent::Describe),
392+
resolution.clone().unique_for_intent(TargetIntent::Describe),
349393
Some(SemanticTarget::Source(_))
350394
));
351395
assert!(matches!(
352-
resolution.clone().for_intent(TargetIntent::Rename),
396+
resolution.clone().unique_for_intent(TargetIntent::Rename),
353397
Some(SemanticTarget::Source(_))
354398
));
355399

@@ -403,6 +447,39 @@ mod tests {
403447
panic!("conflicting source target should be ambiguous");
404448
};
405449

406-
assert_eq!(ambiguity.reason, TargetAmbiguityReason::PreprocHits { candidate_count: 0 });
450+
assert_eq!(ambiguity.reason, TargetAmbiguityReason::PreprocHits { hit_count: 0 });
451+
assert!(ambiguity.candidates.is_empty());
452+
}
453+
454+
#[test]
455+
fn ambiguous_source_target_alternatives_project_as_candidates() {
456+
let range = TextRange::new(TextSize::from(1), TextSize::from(4));
457+
let target_range = TextRange::new(TextSize::from(2), TextSize::from(3));
458+
let target = crate::source_targets::SourceTarget {
459+
origin: crate::source_targets::SourceTargetOrigin::NormalSyntax,
460+
range: target_range,
461+
tokens: Vec::new(),
462+
};
463+
let alternatives = crate::source_targets::SourceTargetAlternatives {
464+
domain: crate::source_targets::SourceTargetDomain::Preproc,
465+
range,
466+
reason: crate::source_targets::SourceTargetAmbiguity::PreprocHits { hit_count: 2 },
467+
targets: vec![target.clone(), target],
468+
};
469+
470+
let resolution = TargetResolution::from_source_resolution(
471+
FileId(0),
472+
crate::source_targets::SourceTargetResolution::Ambiguous(alternatives),
473+
);
474+
475+
assert!(resolution.clone().unique_for_intent(TargetIntent::Describe).is_none());
476+
assert_eq!(resolution.clone().targets_for_intent(TargetIntent::Describe).len(), 2);
477+
478+
let TargetResolution::Ambiguous(alternatives) = resolution else {
479+
panic!("source alternatives should stay ambiguous");
480+
};
481+
assert_eq!(alternatives.anchor.range, range);
482+
assert_eq!(alternatives.reason, TargetAmbiguityReason::PreprocHits { hit_count: 2 });
483+
assert_eq!(alternatives.candidates.len(), 2);
407484
}
408485
}

0 commit comments

Comments
 (0)