Skip to main content

code_moniker_core/core/moniker/
query.rs

1// code-moniker: ignore-file[smell-caller-concentration]
2// TODO(smell): keep bare_callable_name as a moniker query helper shared by local DSL evaluation; revisit if additional callers need a broader query facade.
3use super::{Moniker, MonikerBuilder};
4
5impl Moniker {
6	pub fn is_ancestor_of(&self, other: &Moniker) -> bool {
7		self.as_view().is_ancestor_of(&other.as_view())
8	}
9
10	pub fn parent(&self) -> Option<Moniker> {
11		let view = self.as_view();
12		let n = view.segment_count() as usize;
13		if n == 0 {
14			return None;
15		}
16		let mut b = MonikerBuilder::from_view(view);
17		b.truncate(n - 1);
18		Some(b.build())
19	}
20
21	pub fn last_kind(&self) -> Option<Vec<u8>> {
22		self.as_view().segments().last().map(|s| s.kind.to_vec())
23	}
24
25	pub fn bind_match(&self, other: &Moniker) -> bool {
26		self.as_view().bind_match(&other.as_view())
27	}
28
29	pub fn with_bare_last_segment(&self) -> Moniker {
30		let view = self.as_view();
31		let segs: Vec<_> = view.segments().collect();
32		let Some(last) = segs.last() else {
33			return self.clone();
34		};
35		let bare = bare_callable_name(last.name);
36		if bare.len() == last.name.len() {
37			return self.clone();
38		}
39		let mut b = MonikerBuilder::new();
40		b.project(view.project());
41		for s in &segs[..segs.len() - 1] {
42			b.segment(s.kind, s.name);
43		}
44		b.segment(last.kind, bare);
45		b.build()
46	}
47}
48
49impl<'a> super::MonikerView<'a> {
50	pub fn bind_match(&self, other: &super::MonikerView<'_>) -> bool {
51		if self.project() != other.project() {
52			return false;
53		}
54		let mut ls = self.segments();
55		let mut rs = other.segments();
56		let mut prev_l = match ls.next() {
57			Some(s) => s,
58			None => return false,
59		};
60		let mut prev_r = match rs.next() {
61			Some(s) => s,
62			None => return false,
63		};
64		loop {
65			match (ls.next(), rs.next()) {
66				(None, None) => return last_segment_match(self, other, prev_l.name, prev_r.name),
67				(Some(_), None) | (None, Some(_)) => return false,
68				(Some(nl), Some(nr)) => {
69					if prev_l != prev_r {
70						return false;
71					}
72					prev_l = nl;
73					prev_r = nr;
74				}
75			}
76		}
77	}
78
79	pub fn lang_segment(&self) -> Option<&[u8]> {
80		self.segments().find(|s| s.kind == b"lang").map(|s| s.name)
81	}
82}
83
84fn last_segment_match(
85	left: &super::MonikerView<'_>,
86	right: &super::MonikerView<'_>,
87	l_name: &[u8],
88	r_name: &[u8],
89) -> bool {
90	if l_name == r_name {
91		return true;
92	}
93	let l_lang = left.lang_segment();
94	let r_lang = right.lang_segment();
95	if let Some(lang) = l_lang
96		&& l_lang == r_lang
97	{
98		#[allow(clippy::match_same_arms)]
99		match lang {
100			b"sql" => return bare_callable_name(l_name) == bare_callable_name(r_name),
101			b"ts" => return bare_callable_name(l_name) == bare_callable_name(r_name),
102			b"java" => return bare_callable_name(l_name) == bare_callable_name(r_name),
103			b"python" => return bare_callable_name(l_name) == bare_callable_name(r_name),
104			b"rs" => return bare_callable_name(l_name) == bare_callable_name(r_name),
105			b"go" => return bare_callable_name(l_name) == bare_callable_name(r_name),
106			b"cs" => return bare_callable_name(l_name) == bare_callable_name(r_name),
107			_ => {}
108		}
109	}
110	false
111}
112
113pub fn bare_callable_name(name: &[u8]) -> &[u8] {
114	match name.iter().position(|b| *b == b'(') {
115		Some(i) => &name[..i],
116		None => name,
117	}
118}
119
120#[cfg(test)]
121mod tests {
122	use super::*;
123
124	fn mk(project: &[u8], segs: &[(&[u8], &[u8])]) -> Moniker {
125		let mut b = MonikerBuilder::new();
126		b.project(project);
127		for (k, name) in segs {
128			b.segment(k, name);
129		}
130		b.build()
131	}
132
133	#[test]
134	fn ancestor_is_reflexive() {
135		let m = mk(b"app", &[(b"path", b"a"), (b"path", b"b")]);
136		assert!(m.is_ancestor_of(&m));
137	}
138
139	#[test]
140	fn ancestor_of_strict_prefix() {
141		let parent = mk(b"app", &[(b"path", b"a")]);
142		let child = mk(b"app", &[(b"path", b"a"), (b"path", b"b")]);
143		assert!(parent.is_ancestor_of(&child));
144		assert!(!child.is_ancestor_of(&parent));
145	}
146
147	#[test]
148	fn ancestor_rejects_different_project() {
149		let a = mk(b"app1", &[(b"path", b"x")]);
150		let b = mk(b"app2", &[(b"path", b"x"), (b"path", b"y")]);
151		assert!(!a.is_ancestor_of(&b));
152	}
153
154	#[test]
155	fn ancestor_rejects_diverging_segment() {
156		let a = mk(b"app", &[(b"path", b"a"), (b"path", b"b")]);
157		let b = mk(b"app", &[(b"path", b"a"), (b"path", b"c")]);
158		assert!(!a.is_ancestor_of(&b));
159	}
160
161	#[test]
162	fn parent_drops_last_segment() {
163		let m = mk(b"app", &[(b"path", b"a"), (b"path", b"b")]);
164		let p = m.parent().unwrap();
165		let expected = mk(b"app", &[(b"path", b"a")]);
166		assert_eq!(p, expected);
167	}
168
169	#[test]
170	fn parent_of_project_only_is_none() {
171		let m = mk(b"app", &[]);
172		assert!(m.parent().is_none());
173	}
174
175	#[test]
176	fn parent_of_one_segment_is_project_only() {
177		let m = mk(b"app", &[(b"path", b"a")]);
178		let p = m.parent().unwrap();
179		assert_eq!(p.as_view().segment_count(), 0);
180		assert_eq!(p.as_view().project(), b"app");
181	}
182
183	#[test]
184	fn last_kind_returns_kind_of_last_segment() {
185		let m = mk(b"app", &[(b"path", b"a"), (b"class", b"Foo")]);
186		assert_eq!(m.last_kind(), Some(b"class".to_vec()));
187	}
188
189	#[test]
190	fn last_kind_is_none_for_project_only() {
191		let m = mk(b"app", &[]);
192		assert!(m.last_kind().is_none());
193	}
194
195	#[test]
196	fn byte_lex_is_tree_friendly() {
197		let m1 = mk(b"app", &[(b"class", b"Foo")]);
198		let descendant = mk(b"app", &[(b"class", b"Foo"), (b"method", b"bar()")]);
199		let deeper = mk(
200			b"app",
201			&[(b"class", b"Foo"), (b"method", b"bar()"), (b"path", b"x")],
202		);
203		let sibling = mk(b"app", &[(b"class", b"Zoo")]);
204
205		assert!(m1.as_encoded() < descendant.as_encoded());
206		assert!(descendant.as_encoded() < deeper.as_encoded());
207		assert!(deeper.as_encoded() < sibling.as_encoded());
208	}
209
210	#[test]
211	fn descendant_with_longer_name_stays_inside_parent_range() {
212		let parent = mk(b"app", &[(b"class", b"Foo")]);
213		let child_long = mk(
214			b"app",
215			&[
216				(b"class", b"Foo"),
217				(b"method", b"bar_longer_than_anything()"),
218			],
219		);
220		let next_sibling = mk(b"app", &[(b"class", b"Zoo")]);
221
222		assert!(parent.as_encoded() < child_long.as_encoded());
223		assert!(child_long.as_encoded() < next_sibling.as_encoded());
224	}
225
226	#[test]
227	fn bind_match_equal_monikers_match() {
228		let m = mk(b"app", &[(b"class", b"Foo")]);
229		assert!(m.bind_match(&m));
230	}
231
232	#[test]
233	fn bind_match_path_vs_class_on_last_segment_matches() {
234		let import = mk(b"app", &[(b"module", b"util"), (b"path", b"Foo")]);
235		let def = mk(b"app", &[(b"module", b"util"), (b"class", b"Foo")]);
236		assert!(import.bind_match(&def));
237		assert!(def.bind_match(&import));
238	}
239
240	#[test]
241	fn bind_match_rejects_different_projects() {
242		let l = mk(b"app1", &[(b"class", b"Foo")]);
243		let r = mk(b"app2", &[(b"class", b"Foo")]);
244		assert!(!l.bind_match(&r));
245	}
246
247	#[test]
248	fn bind_match_rejects_different_lang_segment() {
249		let l = mk(b"app", &[(b"lang", b"python"), (b"class", b"Foo")]);
250		let r = mk(b"app", &[(b"lang", b"java"), (b"class", b"Foo")]);
251		assert!(!l.bind_match(&r));
252	}
253
254	#[test]
255	fn bind_match_rejects_different_parent_segment_kind() {
256		let l = mk(b"app", &[(b"package", b"acme"), (b"class", b"Foo")]);
257		let r = mk(b"app", &[(b"path", b"acme"), (b"class", b"Foo")]);
258		assert!(!l.bind_match(&r));
259	}
260
261	#[test]
262	fn bind_match_rejects_different_segment_count() {
263		let shallow = mk(b"app", &[(b"class", b"Foo")]);
264		let deep = mk(b"app", &[(b"class", b"Foo"), (b"method", b"bar()")]);
265		assert!(!shallow.bind_match(&deep));
266	}
267
268	#[test]
269	fn bind_match_rejects_project_only_monikers() {
270		let l = mk(b"app", &[]);
271		let r = mk(b"app", &[]);
272		assert!(!l.bind_match(&r));
273	}
274
275	#[test]
276	fn bind_match_rejects_different_last_segment_name() {
277		let l = mk(b"app", &[(b"class", b"Foo")]);
278		let r = mk(b"app", &[(b"class", b"Bar")]);
279		assert!(!l.bind_match(&r));
280	}
281
282	#[test]
283	fn bind_match_sql_arity_call_matches_typed_def() {
284		let typed_def = mk(
285			b"app",
286			&[
287				(b"lang", b"sql"),
288				(b"schema", b"esac"),
289				(b"module", b"plan"),
290				(b"function", b"create_plan(uuid,text)"),
291			],
292		);
293		let arity_call = mk(
294			b"app",
295			&[
296				(b"lang", b"sql"),
297				(b"schema", b"esac"),
298				(b"module", b"plan"),
299				(b"function", b"create_plan(2)"),
300			],
301		);
302		assert!(arity_call.bind_match(&typed_def));
303		assert!(typed_def.bind_match(&arity_call));
304	}
305
306	#[test]
307	fn bind_match_sql_bare_name_matches_typed_def() {
308		let typed_def = mk(
309			b"app",
310			&[
311				(b"lang", b"sql"),
312				(b"module", b"plan"),
313				(b"function", b"create_plan(uuid,text)"),
314			],
315		);
316		let bare_call = mk(
317			b"app",
318			&[
319				(b"lang", b"sql"),
320				(b"module", b"plan"),
321				(b"function", b"create_plan"),
322			],
323		);
324		assert!(bare_call.bind_match(&typed_def));
325	}
326
327	#[test]
328	fn bind_match_sql_different_bare_names_do_not_match() {
329		let a = mk(
330			b"app",
331			&[
332				(b"lang", b"sql"),
333				(b"module", b"plan"),
334				(b"function", b"create_plan(uuid)"),
335			],
336		);
337		let b = mk(
338			b"app",
339			&[
340				(b"lang", b"sql"),
341				(b"module", b"plan"),
342				(b"function", b"drop_plan(uuid)"),
343			],
344		);
345		assert!(!a.bind_match(&b));
346	}
347
348	#[test]
349	fn bind_match_ts_typed_def_matches_arity_call() {
350		let typed_def = mk(
351			b"app",
352			&[
353				(b"lang", b"ts"),
354				(b"module", b"util"),
355				(b"function", b"foo(number)"),
356			],
357		);
358		let arity_call = mk(
359			b"app",
360			&[
361				(b"lang", b"ts"),
362				(b"module", b"util"),
363				(b"function", b"foo(1)"),
364			],
365		);
366		assert!(typed_def.bind_match(&arity_call));
367		assert!(arity_call.bind_match(&typed_def));
368	}
369
370	#[test]
371	fn bind_match_ts_typed_def_matches_bare_read() {
372		let typed_def = mk(
373			b"app",
374			&[
375				(b"lang", b"ts"),
376				(b"module", b"util"),
377				(b"function", b"foo(number,number)"),
378			],
379		);
380		let bare_read = mk(
381			b"app",
382			&[
383				(b"lang", b"ts"),
384				(b"module", b"util"),
385				(b"function", b"foo()"),
386			],
387		);
388		assert!(typed_def.bind_match(&bare_read));
389	}
390
391	#[test]
392	fn bind_match_ts_distinct_bare_names_do_not_match() {
393		let foo = mk(
394			b"app",
395			&[
396				(b"lang", b"ts"),
397				(b"module", b"util"),
398				(b"function", b"foo(number)"),
399			],
400		);
401		let bar = mk(
402			b"app",
403			&[
404				(b"lang", b"ts"),
405				(b"module", b"util"),
406				(b"function", b"bar(1)"),
407			],
408		);
409		assert!(!foo.bind_match(&bar));
410	}
411
412	#[test]
413	fn bind_match_java_typed_def_matches_arity_call() {
414		let typed_def = mk(
415			b"app",
416			&[
417				(b"lang", b"java"),
418				(b"package", b"acme"),
419				(b"class", b"Plan"),
420				(b"method", b"create(int)"),
421			],
422		);
423		let arity_call = mk(
424			b"app",
425			&[
426				(b"lang", b"java"),
427				(b"package", b"acme"),
428				(b"class", b"Plan"),
429				(b"method", b"create(1)"),
430			],
431		);
432		assert!(typed_def.bind_match(&arity_call));
433	}
434
435	#[test]
436	fn bind_match_java_typed_def_matches_bare_read() {
437		let typed_def = mk(
438			b"app",
439			&[
440				(b"lang", b"java"),
441				(b"package", b"acme"),
442				(b"class", b"Plan"),
443				(b"method", b"create(int)"),
444			],
445		);
446		let bare = mk(
447			b"app",
448			&[
449				(b"lang", b"java"),
450				(b"package", b"acme"),
451				(b"class", b"Plan"),
452				(b"method", b"create"),
453			],
454		);
455		assert!(typed_def.bind_match(&bare));
456	}
457
458	#[test]
459	fn bind_match_python_typed_def_matches_arity_call() {
460		let typed_def = mk(
461			b"app",
462			&[
463				(b"lang", b"python"),
464				(b"module", b"m"),
465				(b"function", b"f(int)"),
466			],
467		);
468		let arity_call = mk(
469			b"app",
470			&[
471				(b"lang", b"python"),
472				(b"module", b"m"),
473				(b"function", b"f(1)"),
474			],
475		);
476		assert!(typed_def.bind_match(&arity_call));
477	}
478
479	#[test]
480	fn bind_match_rust_typed_def_matches_arity_call() {
481		let typed_def = mk(
482			b"app",
483			&[
484				(b"lang", b"rs"),
485				(b"module", b"util"),
486				(b"function", b"add(i32,i32)"),
487			],
488		);
489		let arity_call = mk(
490			b"app",
491			&[
492				(b"lang", b"rs"),
493				(b"module", b"util"),
494				(b"function", b"add(2)"),
495			],
496		);
497		assert!(typed_def.bind_match(&arity_call));
498	}
499
500	#[test]
501	fn bind_match_csharp_typed_def_matches_arity_call() {
502		let typed_def = mk(
503			b"app",
504			&[
505				(b"lang", b"cs"),
506				(b"namespace", b"Acme"),
507				(b"class", b"Plan"),
508				(b"method", b"Add(int,int)"),
509			],
510		);
511		let arity_call = mk(
512			b"app",
513			&[
514				(b"lang", b"cs"),
515				(b"namespace", b"Acme"),
516				(b"class", b"Plan"),
517				(b"method", b"Add(2)"),
518			],
519		);
520		assert!(typed_def.bind_match(&arity_call));
521		assert!(arity_call.bind_match(&typed_def));
522	}
523
524	#[test]
525	fn bind_match_csharp_typed_constructor_matches_arity_call() {
526		let typed_def = mk(
527			b"app",
528			&[
529				(b"lang", b"cs"),
530				(b"namespace", b"Acme"),
531				(b"class", b"Plan"),
532				(b"constructor", b"Plan(int,string)"),
533			],
534		);
535		let arity_call = mk(
536			b"app",
537			&[
538				(b"lang", b"cs"),
539				(b"namespace", b"Acme"),
540				(b"class", b"Plan"),
541				(b"constructor", b"Plan(2)"),
542			],
543		);
544		assert!(typed_def.bind_match(&arity_call));
545	}
546
547	#[test]
548	fn bind_match_csharp_distinct_bare_names_do_not_match() {
549		let foo = mk(
550			b"app",
551			&[
552				(b"lang", b"cs"),
553				(b"class", b"Plan"),
554				(b"method", b"Add(int)"),
555			],
556		);
557		let bar = mk(
558			b"app",
559			&[
560				(b"lang", b"cs"),
561				(b"class", b"Plan"),
562				(b"method", b"Subtract(1)"),
563			],
564		);
565		assert!(!foo.bind_match(&bar));
566	}
567}