1use std::collections::{BTreeMap, HashMap};
18use std::path::{Path, PathBuf};
19
20use bynk_check::checker::Ty;
21use bynk_check::index::{ProjectIndex, SiteRef, SymbolKey, SymbolKind};
22use bynk_syntax::span::Span;
23
24pub fn sites_for<'a>(
26 index: &'a ProjectIndex,
27 path: &Path,
28 offset: usize,
29 include_declaration: bool,
30) -> Option<Vec<&'a SiteRef>> {
31 let (key, _) = index.symbol_at(path, offset)?;
32 let mut sites = index.sites(key);
33 if !include_declaration && !sites.is_empty() {
34 sites.remove(0); }
36 Some(sites)
37}
38
39pub fn definition_at<'a>(
42 index: &'a ProjectIndex,
43 path: &Path,
44 offset: usize,
45) -> Option<(&'a SymbolKey, &'a SiteRef)> {
46 let (key, _) = index.symbol_at(path, offset)?;
47 let entry = index.symbols.get(key)?;
48 Some((key, entry.def.as_ref()?))
49}
50
51pub fn prepare_rename<'a>(
55 index: &'a ProjectIndex,
56 path: &Path,
57 offset: usize,
58) -> Option<(&'a SymbolKey, &'a SiteRef)> {
59 index.symbol_at(path, offset)
60}
61
62pub fn workspace_symbols<'a>(
66 index: &'a ProjectIndex,
67 query: &str,
68) -> Vec<(&'a SymbolKey, &'a SiteRef)> {
69 let q = query.to_lowercase();
70 let mut out: Vec<_> = index
71 .symbols
72 .iter()
73 .filter(|(k, _)| q.is_empty() || k.name.to_lowercase().contains(&q))
74 .filter_map(|(k, e)| e.def.as_ref().map(|d| (k, d)))
75 .collect();
76 out.sort_by(|a, b| (&a.0.name, &a.0.unit).cmp(&(&b.0.name, &b.0.unit)));
77 out
78}
79
80pub fn code_lenses<'a>(index: &'a ProjectIndex, path: &Path) -> Vec<(&'a SiteRef, &'a [SiteRef])> {
85 let mut out: Vec<(&SiteRef, &[SiteRef])> = index
86 .symbols
87 .values()
88 .filter_map(|e| {
89 let def = e.def.as_ref()?;
90 (def.path == path).then_some((def, e.refs.as_slice()))
91 })
92 .collect();
93 out.sort_by_key(|(def, _)| (def.span.start, def.span.end));
94 out
95}
96
97pub struct CallRelation<'a> {
104 pub key: &'a SymbolKey,
105 pub def: &'a SiteRef,
106 pub sites: Vec<&'a SiteRef>,
107}
108
109pub fn prepare_call_hierarchy<'a>(
113 index: &'a ProjectIndex,
114 path: &Path,
115 offset: usize,
116) -> Option<(&'a SymbolKey, &'a SiteRef)> {
117 definition_at(index, path, offset)
118}
119
120fn group_calls<'a>(
126 index: &'a ProjectIndex,
127 edges: impl Iterator<Item = &'a bynk_check::index::CallEdge>,
128 pick: impl Fn(&'a bynk_check::index::CallEdge) -> &'a SymbolKey,
129) -> Vec<CallRelation<'a>> {
130 let mut by_key: BTreeMap<&SymbolKey, Vec<&SiteRef>> = BTreeMap::new();
131 for edge in edges {
132 by_key.entry(pick(edge)).or_default().push(&edge.site);
133 }
134 let mut out: Vec<CallRelation<'a>> = by_key
135 .into_iter()
136 .filter_map(|(key, mut sites)| {
137 let def = index.symbols.get(key)?.def.as_ref()?;
138 sites.sort();
139 Some(CallRelation { key, def, sites })
140 })
141 .collect();
142 out.sort_by_key(|r| (r.def.path.clone(), r.def.span.start, r.def.span.end));
143 out
144}
145
146pub fn incoming_calls<'a>(index: &'a ProjectIndex, key: &SymbolKey) -> Vec<CallRelation<'a>> {
149 group_calls(index, index.calls_into(key), |e| &e.caller)
150}
151
152pub fn outgoing_calls<'a>(index: &'a ProjectIndex, key: &SymbolKey) -> Vec<CallRelation<'a>> {
155 group_calls(index, index.calls_from(key), |e| &e.callee)
156}
157
158pub fn implementations<'a>(index: &'a ProjectIndex, key: &SymbolKey) -> Vec<&'a SiteRef> {
163 let mut defs: Vec<&SiteRef> = index
164 .impls_of(key)
165 .filter_map(|e| index.symbols.get(&e.provider)?.def.as_ref())
166 .collect();
167 defs.sort_by_key(|d| (d.path.clone(), d.span.start, d.span.end));
168 defs.dedup();
169 defs
170}
171
172pub fn type_definitions_named<'a>(index: &'a ProjectIndex, name: &str) -> Vec<&'a SiteRef> {
178 let mut defs: Vec<&SiteRef> = index
179 .symbols
180 .iter()
181 .filter(|(k, _)| k.kind == SymbolKind::Type && k.name == name)
182 .filter_map(|(_, e)| e.def.as_ref())
183 .collect();
184 defs.sort_by_key(|d| (d.path.clone(), d.span.start, d.span.end));
185 defs.dedup();
186 defs
187}
188
189pub fn named_type_target(ty: &Ty) -> Option<&str> {
195 match ty {
196 Ty::Named { name, .. } => Some(name),
197 Ty::Option(t) | Ty::Effect(t) | Ty::List(t) | Ty::HttpResult(t) => named_type_target(t),
198 _ => None,
199 }
200}
201
202pub fn document_highlights<'a>(
207 index: &'a ProjectIndex,
208 path: &Path,
209 offset: usize,
210) -> Option<Vec<&'a SiteRef>> {
211 let sites = sites_for(index, path, offset, true)?;
212 Some(sites.into_iter().filter(|s| s.path == path).collect())
213}
214
215#[derive(Debug, Clone)]
218pub struct RenamePlan {
219 pub key: SymbolKey,
220 pub new_name: String,
221 pub edits: BTreeMap<PathBuf, Vec<Span>>,
222}
223
224pub fn plan_rename(
227 index: &ProjectIndex,
228 path: &Path,
229 offset: usize,
230 new_name: &str,
231) -> Result<RenamePlan, String> {
232 validate_new_name(new_name)?;
233 let (key, _) = index.symbol_at(path, offset).ok_or_else(|| {
234 "no renameable symbol at the cursor — types, fns, methods, record fields, \
235 capability ops, capabilities, services, agents and providers rename; \
236 local bindings and unit names are not yet supported"
237 .to_string()
238 })?;
239 if key_segment(&key.name) == new_name {
240 return Err(format!("`{new_name}` is already the symbol's name"));
241 }
242 let mut edits: BTreeMap<PathBuf, Vec<Span>> = BTreeMap::new();
243 for site in index.sites(key) {
244 edits.entry(site.path.clone()).or_default().push(site.span);
245 }
246 for spans in edits.values_mut() {
247 spans.sort();
248 spans.dedup();
249 }
250 Ok(RenamePlan {
251 key: key.clone(),
252 new_name: new_name.to_string(),
253 edits,
254 })
255}
256
257pub fn validate_new_name(name: &str) -> Result<(), String> {
260 let err = || format!("`{name}` is not a valid Bynk identifier");
261 let tokens = bynk_syntax::lexer::tokenize(name).map_err(|_| err())?;
262 match tokens.as_slice() {
263 [t] if matches!(t.kind, bynk_syntax::lexer::TokenKind::Ident)
264 && t.span.start == 0
265 && t.span.end == name.len() =>
266 {
267 Ok(())
268 }
269 _ => Err(err()),
270 }
271}
272
273pub fn apply_edits(text: &str, spans: &[Span], new_name: &str) -> String {
275 let mut out = String::with_capacity(text.len());
276 let mut last = 0;
277 for s in spans {
278 out.push_str(&text[last..s.start]);
279 out.push_str(new_name);
280 last = s.end;
281 }
282 out.push_str(&text[last..]);
283 out
284}
285
286pub fn remap_site(site: &SiteRef, plan: &RenamePlan) -> SiteRef {
289 let Some(spans) = plan.edits.get(&site.path) else {
290 return site.clone();
291 };
292 let delta = plan.new_name.len() as isize - key_segment(&plan.key.name).len() as isize;
295 let shift: isize = spans.iter().filter(|s| s.end <= site.span.start).count() as isize * delta;
296 let start = (site.span.start as isize + shift) as usize;
297 let end = if spans.binary_search(&site.span).is_ok() {
298 start + plan.new_name.len()
299 } else {
300 (site.span.end as isize + shift) as usize
301 };
302 SiteRef {
303 path: site.path.clone(),
304 span: Span::new(start, end),
305 }
306}
307
308pub fn index_unchanged_modulo_rename(
313 pre: &ProjectIndex,
314 post: &ProjectIndex,
315 plan: &RenamePlan,
316) -> bool {
317 let target = renamed_key_name(&plan.key.name, &plan.new_name);
321 pre.equals_modulo_rename(post, &plan.key, &target, |s| remap_site(s, plan))
322}
323
324fn renamed_key_name(old: &str, new_segment: &str) -> String {
328 match old.rfind('.') {
329 Some(i) => format!("{}.{new_segment}", &old[..i]),
330 None => new_segment.to_string(),
331 }
332}
333
334fn key_segment(name: &str) -> &str {
337 name.rsplit('.').next().unwrap_or(name)
338}
339
340pub fn no_new_diagnostics(
344 pre: &[(PathBuf, String)],
345 post: &[(PathBuf, String)],
346) -> Result<(), String> {
347 let mut budget: HashMap<(&Path, &str), isize> = HashMap::new();
348 for (p, c) in pre {
349 *budget.entry((p.as_path(), c.as_str())).or_default() += 1;
350 }
351 for (p, c) in post {
352 let n = budget.entry((p.as_path(), c.as_str())).or_default();
353 *n -= 1;
354 if *n < 0 {
355 return Err(format!(
356 "rename would introduce `{c}` in {} — refused",
357 p.display()
358 ));
359 }
360 }
361 Ok(())
362}
363
364pub fn semantic_tokens_legend() -> tower_lsp::lsp_types::SemanticTokensLegend {
372 use tower_lsp::lsp_types::{SemanticTokenModifier, SemanticTokenType, SemanticTokensLegend};
373 SemanticTokensLegend {
374 token_types: vec![
375 SemanticTokenType::TYPE,
376 SemanticTokenType::FUNCTION,
377 SemanticTokenType::new("capability"),
378 SemanticTokenType::new("service"),
379 SemanticTokenType::new("agent"),
380 SemanticTokenType::new("provider"),
381 SemanticTokenType::VARIABLE,
384 SemanticTokenType::METHOD,
387 SemanticTokenType::PROPERTY,
390 SemanticTokenType::new("actor"),
393 ],
394 token_modifiers: vec![
395 SemanticTokenModifier::DECLARATION,
396 SemanticTokenModifier::new("refined"),
397 SemanticTokenModifier::new("opaque"),
398 SemanticTokenModifier::new("platformNative"),
399 ],
400 }
401}
402
403fn token_type_index(kind: SymbolKind) -> u32 {
405 match kind {
406 SymbolKind::Type => 0,
407 SymbolKind::Fn => 1,
408 SymbolKind::Capability => 2,
409 SymbolKind::Service => 3,
410 SymbolKind::Agent => 4,
411 SymbolKind::Provider => 5,
412 SymbolKind::Method => 7,
414 SymbolKind::CapabilityOp => 7,
416 SymbolKind::Field => 8,
417 SymbolKind::Actor => 9,
419 }
420}
421
422const TOK_LOCAL: u32 = 6;
424
425const MOD_DECLARATION: u32 = 1 << 0;
426const MOD_REFINED: u32 = 1 << 1;
427const MOD_OPAQUE: u32 = 1 << 2;
428const MOD_PLATFORM_NATIVE: u32 = 1 << 3;
429
430fn modifier_bits(m: bynk_check::index::SymbolModifiers) -> u32 {
431 (if m.refined { MOD_REFINED } else { 0 })
432 | (if m.opaque { MOD_OPAQUE } else { 0 })
433 | (if m.platform_native {
434 MOD_PLATFORM_NATIVE
435 } else {
436 0
437 })
438}
439
440pub fn semantic_tokens(
447 index: &ProjectIndex,
448 local_tokens: &[(Span, bool)],
449 path: &Path,
450 text: &str,
451 range: Option<Span>,
452) -> Vec<tower_lsp::lsp_types::SemanticToken> {
453 let in_scope = |span: Span| {
454 span.end <= text.len() && range.is_none_or(|r| span.end > r.start && span.start < r.end)
455 };
456 let mut raw: Vec<(Span, u32, u32)> = Vec::new();
457 for (key, entry) in &index.symbols {
458 let ty = token_type_index(key.kind);
459 let mods = modifier_bits(entry.modifiers);
460 if let Some(def) = &entry.def
461 && def.path == path
462 && in_scope(def.span)
463 {
464 raw.push((def.span, ty, mods | MOD_DECLARATION));
465 }
466 for site in &entry.refs {
467 if site.path == path && in_scope(site.span) {
468 raw.push((site.span, ty, mods));
469 }
470 }
471 }
472 for fr in &index.foreign_refs {
473 if fr.site.path == path && in_scope(fr.site.span) {
474 raw.push((
475 fr.site.span,
476 token_type_index(fr.kind),
477 modifier_bits(fr.modifiers),
478 ));
479 }
480 }
481 for &(span, is_decl) in local_tokens {
486 if in_scope(span) {
487 raw.push((span, TOK_LOCAL, if is_decl { MOD_DECLARATION } else { 0 }));
488 }
489 }
490 raw.sort_by_key(|(span, _, _)| (span.start, span.end));
493 let mut data = Vec::with_capacity(raw.len());
494 let (mut prev_line, mut prev_start) = (0u32, 0u32);
495 for (span, token_type, token_modifiers_bitset) in raw {
496 let pos = crate::position::offset_to_position(text, span.start);
497 let delta_line = pos.line - prev_line;
498 let delta_start = if delta_line == 0 {
499 pos.character - prev_start
500 } else {
501 pos.character
502 };
503 data.push(tower_lsp::lsp_types::SemanticToken {
504 delta_line,
505 delta_start,
506 length: text[span.range()].encode_utf16().count() as u32,
509 token_type,
510 token_modifiers_bitset,
511 });
512 prev_line = pos.line;
513 prev_start = pos.character;
514 }
515 data
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use bynk_check::index::{SymbolEntry, SymbolKind};
522
523 fn site(path: &str, start: usize, end: usize) -> SiteRef {
524 SiteRef {
525 path: PathBuf::from(path),
526 span: Span::new(start, end),
527 }
528 }
529
530 fn key(unit: &str, kind: SymbolKind, name: &str) -> SymbolKey {
531 SymbolKey {
532 unit: unit.into(),
533 kind,
534 name: name.into(),
535 }
536 }
537
538 fn index_with(entries: Vec<(SymbolKey, SiteRef, Vec<SiteRef>)>) -> ProjectIndex {
539 let mut index = ProjectIndex::default();
540 for (k, def, refs) in entries {
541 index.symbols.insert(
542 k,
543 SymbolEntry {
544 def: Some(def),
545 refs,
546 ..Default::default()
547 },
548 );
549 }
550 index
551 }
552
553 #[test]
554 fn new_name_validation() {
555 assert!(validate_new_name("Money2").is_ok());
556 assert!(validate_new_name("snake_case").is_ok());
557 assert!(validate_new_name("fn").is_err(), "keyword");
558 assert!(validate_new_name("two words").is_err());
559 assert!(validate_new_name("1abc").is_err());
560 assert!(validate_new_name("a.b").is_err());
561 assert!(validate_new_name("").is_err());
562 }
563
564 #[test]
565 fn apply_and_remap_agree() {
566 let text = "fn helper(x: Int) -> Int { helper(x) }";
569 let k = key("demo.a", SymbolKind::Fn, "helper");
570 let index = index_with(vec![(
571 k.clone(),
572 site("a.bynk", 3, 9),
573 vec![site("a.bynk", 27, 33)],
574 )]);
575 let plan = plan_rename(&index, Path::new("a.bynk"), 4, "do_it").unwrap();
576 let edited = apply_edits(text, &plan.edits[Path::new("a.bynk")], "do_it");
577 assert_eq!(edited, "fn do_it(x: Int) -> Int { do_it(x) }");
578
579 for (old, expected) in [
581 (site("a.bynk", 3, 9), "do_it"),
582 (site("a.bynk", 27, 33), "do_it"),
583 ] {
584 let new = remap_site(&old, &plan);
585 assert_eq!(&edited[new.span.range()], expected);
586 }
587 let unrelated = site("a.bynk", 34, 35); let new = remap_site(&unrelated, &plan);
590 assert_eq!(&edited[new.span.range()], "x");
591 }
592
593 #[test]
594 fn references_listing_orders_definition_first() {
595 let k = key("demo.a", SymbolKind::Type, "Money");
596 let index = index_with(vec![(
597 k,
598 site("a.bynk", 5, 10),
599 vec![site("b.bynk", 1, 6), site("a.bynk", 20, 25)],
600 )]);
601 let all = sites_for(&index, Path::new("b.bynk"), 3, true).unwrap();
602 assert_eq!(all.len(), 3);
603 assert_eq!(all[0].path, PathBuf::from("a.bynk"));
604 assert_eq!(all[0].span, Span::new(5, 10));
605 let without_decl = sites_for(&index, Path::new("b.bynk"), 3, false).unwrap();
606 assert_eq!(without_decl.len(), 2);
607 }
608
609 #[test]
610 fn rename_refuses_unindexed_positions_and_same_name() {
611 let k = key("demo.a", SymbolKind::Fn, "helper");
612 let index = index_with(vec![(k, site("a.bynk", 3, 9), vec![])]);
613 assert!(plan_rename(&index, Path::new("a.bynk"), 100, "x").is_err());
614 assert!(plan_rename(&index, Path::new("a.bynk"), 4, "helper").is_err());
615 }
616
617 #[test]
618 fn index_equality_detects_escape() {
619 let helper = key("demo.a", SymbolKind::Fn, "helper");
621 let money = key("demo.a", SymbolKind::Type, "Money");
622 let pre = index_with(vec![
623 (helper.clone(), site("a.bynk", 3, 9), vec![]),
624 (money.clone(), site("a.bynk", 50, 55), vec![]),
625 ]);
626 let plan = plan_rename(&pre, Path::new("a.bynk"), 4, "shadow").unwrap();
627
628 let honest = index_with(vec![
630 (
631 key("demo.a", SymbolKind::Fn, "shadow"),
632 site("a.bynk", 3, 9),
633 vec![],
634 ),
635 (money.clone(), site("a.bynk", 50, 55), vec![]),
636 ]);
637 assert!(index_unchanged_modulo_rename(&pre, &honest, &plan));
638
639 let escape = index_with(vec![
642 (
643 key("demo.a", SymbolKind::Fn, "shadow"),
644 site("a.bynk", 3, 9),
645 vec![site("a.bynk", 70, 76)],
646 ),
647 (money, site("a.bynk", 50, 55), vec![]),
648 ]);
649 assert!(!index_unchanged_modulo_rename(&pre, &escape, &plan));
650 }
651
652 #[test]
653 fn method_rename_edits_the_member_segment_and_remaps_the_compound_key() {
654 let bump = key("demo.a", SymbolKind::Method, "Counter.bump");
659 let other = key("demo.a", SymbolKind::Type, "Counter");
660 let pre = index_with(vec![
661 (other.clone(), site("a.bynk", 0, 5), vec![]),
662 (
664 bump.clone(),
665 site("a.bynk", 11, 15),
666 vec![site("a.bynk", 40, 44)],
667 ),
668 ]);
669
670 let plan = plan_rename(&pre, Path::new("a.bynk"), 12, "increment").unwrap();
672 assert_eq!(plan.key.name, "Counter.bump");
673 assert_eq!(plan.new_name, "increment");
674 assert!(plan_rename(&pre, Path::new("a.bynk"), 12, "bump").is_err());
676
677 let post = index_with(vec![
680 (other, site("a.bynk", 0, 5), vec![]),
681 (
682 key("demo.a", SymbolKind::Method, "Counter.increment"),
683 site("a.bynk", 11, 20),
684 vec![site("a.bynk", 45, 54)],
685 ),
686 ]);
687 assert!(
688 index_unchanged_modulo_rename(&pre, &post, &plan),
689 "compound key remaps to Counter.increment and segment-based delta lines the spans up"
690 );
691 }
692
693 #[test]
694 fn workspace_symbols_filters_and_orders() {
695 let index = index_with(vec![
696 (
697 key("demo.a", SymbolKind::Type, "Money"),
698 site("a.bynk", 5, 10),
699 vec![],
700 ),
701 (
702 key("demo.b", SymbolKind::Fn, "moneyMaker"),
703 site("b.bynk", 3, 13),
704 vec![],
705 ),
706 (
707 key("demo.a", SymbolKind::Fn, "helper"),
708 site("a.bynk", 40, 46),
709 vec![],
710 ),
711 ]);
712 let hits = workspace_symbols(&index, "money");
714 assert_eq!(
715 hits.iter()
716 .map(|(k, _)| k.name.as_str())
717 .collect::<Vec<_>>(),
718 vec!["Money", "moneyMaker"]
719 );
720 assert_eq!(workspace_symbols(&index, "").len(), 3);
722 assert!(workspace_symbols(&index, "nothing").is_empty());
723 }
724
725 #[test]
726 fn document_highlights_are_file_scoped() {
727 let k = key("demo.a", SymbolKind::Type, "Money");
728 let index = index_with(vec![(
729 k,
730 site("a.bynk", 5, 10),
731 vec![site("b.bynk", 1, 6), site("a.bynk", 20, 25)],
732 )]);
733 let highlights = document_highlights(&index, Path::new("a.bynk"), 7).unwrap();
735 assert_eq!(highlights.len(), 2);
736 assert!(highlights.iter().all(|s| s.path == Path::new("a.bynk")));
737 assert!(document_highlights(&index, Path::new("a.bynk"), 100).is_none());
739 }
740
741 #[test]
742 fn diagnostic_budget_allows_removals_refuses_additions() {
743 let pre = vec![
744 (PathBuf::from("a.bynk"), "bynk.x".to_string()),
745 (PathBuf::from("a.bynk"), "bynk.x".to_string()),
746 ];
747 let same = pre.clone();
748 assert!(no_new_diagnostics(&pre, &same).is_ok());
749 assert!(no_new_diagnostics(&pre, &pre[..1]).is_ok());
750 let mut more = pre.clone();
751 more.push((PathBuf::from("b.bynk"), "bynk.resolve.duplicate_fn".into()));
752 assert!(no_new_diagnostics(&pre, &more).is_err());
753 }
754
755 #[test]
761 fn legend_is_frozen() {
762 let legend = semantic_tokens_legend();
763 let types: Vec<&str> = legend.token_types.iter().map(|t| t.as_str()).collect();
764 assert_eq!(
765 types,
766 [
767 "type",
768 "function",
769 "capability",
770 "service",
771 "agent",
772 "provider",
773 "variable", "method", "property", "actor", ]
778 );
779 let modifiers: Vec<&str> = legend.token_modifiers.iter().map(|m| m.as_str()).collect();
780 assert_eq!(
781 modifiers,
782 ["declaration", "refined", "opaque", "platformNative"]
783 );
784 }
785
786 #[test]
787 fn code_lenses_count_references_per_definition_in_the_file() {
788 let index = index_with(vec![
789 (
791 key("u", SymbolKind::Fn, "foo"),
792 site("a.bynk", 3, 6),
793 vec![site("a.bynk", 20, 23), site("b.bynk", 4, 7)],
794 ),
795 (
797 key("u", SymbolKind::Type, "Bar"),
798 site("a.bynk", 40, 43),
799 vec![],
800 ),
801 (
803 key("u", SymbolKind::Fn, "qux"),
804 site("b.bynk", 0, 3),
805 vec![],
806 ),
807 ]);
808 let lenses = code_lenses(&index, Path::new("a.bynk"));
809 assert_eq!(lenses.len(), 2, "two a.bynk defs get lenses");
810 assert_eq!((lenses[0].0.span.start, lenses[0].1.len()), (3, 2));
812 assert_eq!((lenses[1].0.span.start, lenses[1].1.len()), (40, 0));
813 assert!(code_lenses(&index, Path::new("c.bynk")).is_empty());
814 }
815
816 #[test]
817 fn call_hierarchy_groups_incoming_and_outgoing_by_symbol() {
818 use bynk_check::index::CallEdge;
819 let mut index = index_with(vec![
823 (key("u", SymbolKind::Fn, "a"), site("f.bynk", 3, 4), vec![]),
824 (
825 key("u", SymbolKind::Fn, "b"),
826 site("f.bynk", 40, 41),
827 vec![],
828 ),
829 (
830 key("u", SymbolKind::Fn, "c"),
831 site("f.bynk", 80, 81),
832 vec![],
833 ),
834 ]);
835 let edge = |caller: &str, cs: usize, ce: usize| CallEdge {
836 caller: key("u", SymbolKind::Fn, caller),
837 callee: key("u", SymbolKind::Fn, "c"),
838 site: site("f.bynk", cs, ce),
839 };
840 index.calls = vec![edge("a", 10, 11), edge("a", 20, 21), edge("b", 50, 51)];
841
842 let into_c = incoming_calls(&index, &key("u", SymbolKind::Fn, "c"));
843 assert_eq!(into_c.len(), 2);
845 assert_eq!(
846 (into_c[0].key.name.as_str(), into_c[0].sites.len()),
847 ("a", 2)
848 );
849 assert_eq!(
850 (into_c[1].key.name.as_str(), into_c[1].sites.len()),
851 ("b", 1)
852 );
853
854 let from_a = outgoing_calls(&index, &key("u", SymbolKind::Fn, "a"));
855 assert_eq!(from_a.len(), 1);
856 assert_eq!(
857 (from_a[0].key.name.as_str(), from_a[0].sites.len()),
858 ("c", 2)
859 );
860
861 assert!(outgoing_calls(&index, &key("u", SymbolKind::Fn, "c")).is_empty());
863 assert!(incoming_calls(&index, &key("u", SymbolKind::Fn, "ghost")).is_empty());
864 }
865
866 #[test]
867 fn implementations_lists_provider_defs_for_a_capability() {
868 use bynk_check::index::ImplEdge;
869 let mut index = index_with(vec![
871 (
872 key("u", SymbolKind::Capability, "Cap"),
873 site("a.bynk", 10, 13),
874 vec![],
875 ),
876 (
877 key("u", SymbolKind::Provider, "P1"),
878 site("a.bynk", 50, 52),
879 vec![],
880 ),
881 (
882 key("u", SymbolKind::Provider, "P2"),
883 site("b.bynk", 5, 7),
884 vec![],
885 ),
886 (
887 key("u", SymbolKind::Capability, "Other"),
888 site("a.bynk", 80, 85),
889 vec![],
890 ),
891 ]);
892 let edge = |provider: &str, file: &str, s: usize, e: usize| ImplEdge {
893 capability: key("u", SymbolKind::Capability, "Cap"),
894 provider: key("u", SymbolKind::Provider, provider),
895 site: site(file, s, e),
896 };
897 index.impls = vec![edge("P1", "a.bynk", 30, 33), edge("P2", "b.bynk", 20, 23)];
898
899 let impls = implementations(&index, &key("u", SymbolKind::Capability, "Cap"));
901 assert_eq!(impls.len(), 2);
902 assert_eq!(
903 (&impls[0].path, impls[0].span.start),
904 (&PathBuf::from("a.bynk"), 50)
905 );
906 assert_eq!(
907 (&impls[1].path, impls[1].span.start),
908 (&PathBuf::from("b.bynk"), 5)
909 );
910
911 assert!(implementations(&index, &key("u", SymbolKind::Capability, "Other")).is_empty());
913 assert!(implementations(&index, &key("u", SymbolKind::Capability, "Ghost")).is_empty());
914 }
915
916 #[test]
917 fn type_definitions_named_collects_type_defs_by_bare_name() {
918 let index = index_with(vec![
920 (
921 key("a", SymbolKind::Type, "Order"),
922 site("a.bynk", 10, 15),
923 vec![],
924 ),
925 (
926 key("b", SymbolKind::Type, "Order"),
927 site("b.bynk", 4, 9),
928 vec![],
929 ),
930 (
931 key("a", SymbolKind::Fn, "Order"),
932 site("a.bynk", 40, 45),
933 vec![],
934 ),
935 ]);
936 let defs = type_definitions_named(&index, "Order");
938 assert_eq!(defs.len(), 2);
939 assert_eq!(
940 (&defs[0].path, defs[0].span.start),
941 (&PathBuf::from("a.bynk"), 10)
942 );
943 assert_eq!(
944 (&defs[1].path, defs[1].span.start),
945 (&PathBuf::from("b.bynk"), 4)
946 );
947 assert!(type_definitions_named(&index, "Nope").is_empty());
949 }
950
951 #[test]
952 fn named_type_target_unwraps_single_param_containers() {
953 use bynk_check::checker::NamedKind;
954 use bynk_syntax::ast::BaseType;
955 let order = || Ty::Named {
956 name: "Order".into(),
957 kind: NamedKind::Record,
958 };
959 assert_eq!(named_type_target(&order()), Some("Order"));
960 assert_eq!(
961 named_type_target(&Ty::Option(Box::new(order()))),
962 Some("Order")
963 );
964 assert_eq!(
966 named_type_target(&Ty::List(Box::new(Ty::Effect(Box::new(order()))))),
967 Some("Order")
968 );
969 assert_eq!(named_type_target(&Ty::Base(BaseType::Int)), None);
971 assert_eq!(
972 named_type_target(&Ty::Result(Box::new(order()), Box::new(order()))),
973 None
974 );
975 assert_eq!(named_type_target(&Ty::Unit), None);
976 }
977
978 #[test]
979 fn tokens_are_delta_encoded_with_modifier_bitsets() {
980 let text = "type Age = Int\nfn f(a: Age) -> Age {}\n";
983 let mut index = index_with(vec![(
984 key("shop", SymbolKind::Type, "Age"),
985 site("a.bynk", 5, 8),
986 vec![site("a.bynk", 23, 26), site("a.bynk", 31, 34)],
987 )]);
988 index
989 .symbols
990 .get_mut(&key("shop", SymbolKind::Type, "Age"))
991 .unwrap()
992 .modifiers = bynk_check::index::SymbolModifiers {
993 refined: true,
994 ..Default::default()
995 };
996 let tokens = semantic_tokens(&index, &[], Path::new("a.bynk"), text, None);
997 assert_eq!(tokens.len(), 3);
998 assert_eq!(
1000 (
1001 tokens[0].delta_line,
1002 tokens[0].delta_start,
1003 tokens[0].length
1004 ),
1005 (0, 5, 3)
1006 );
1007 assert_eq!(tokens[0].token_type, 0);
1008 assert_eq!(tokens[0].token_modifiers_bitset, 0b0011);
1009 assert_eq!(
1011 (
1012 tokens[1].delta_line,
1013 tokens[1].delta_start,
1014 tokens[1].length
1015 ),
1016 (1, 8, 3)
1017 );
1018 assert_eq!(tokens[1].token_modifiers_bitset, 0b0010);
1019 assert_eq!(
1021 (
1022 tokens[2].delta_line,
1023 tokens[2].delta_start,
1024 tokens[2].length
1025 ),
1026 (0, 8, 3)
1027 );
1028 }
1029
1030 #[test]
1031 fn foreign_refs_emit_tokens_and_range_filters() {
1032 let text = "given Kv {\n Kv.get(k)\n}\n";
1033 let mut index = ProjectIndex::default();
1034 index.foreign_refs.push(bynk_check::index::ForeignRef {
1035 site: site("a.bynk", 6, 8),
1036 kind: SymbolKind::Capability,
1037 modifiers: bynk_check::index::SymbolModifiers {
1038 platform_native: true,
1039 ..Default::default()
1040 },
1041 });
1042 index.foreign_refs.push(bynk_check::index::ForeignRef {
1043 site: site("a.bynk", 13, 15),
1044 kind: SymbolKind::Capability,
1045 modifiers: bynk_check::index::SymbolModifiers {
1046 platform_native: true,
1047 ..Default::default()
1048 },
1049 });
1050 let all = semantic_tokens(&index, &[], Path::new("a.bynk"), text, None);
1051 assert_eq!(all.len(), 2);
1052 assert_eq!(all[0].token_type, 2); assert_eq!(all[0].token_modifiers_bitset, 0b1000); let ranged = semantic_tokens(
1056 &index,
1057 &[],
1058 Path::new("a.bynk"),
1059 text,
1060 Some(Span::new(0, 10)),
1061 );
1062 assert_eq!(ranged.len(), 1);
1063 assert!(semantic_tokens(&index, &[], Path::new("b.bynk"), text, None).is_empty());
1065 assert!(
1066 semantic_tokens(
1067 &ProjectIndex::default(),
1068 &[],
1069 Path::new("a.bynk"),
1070 text,
1071 None
1072 )
1073 .is_empty()
1074 );
1075 }
1076}