1use std::fmt;
2use std::ops::Deref;
3
4use ptype::parameter::Parameter;
5use substrait::proto;
6use substrait::proto::r#type::{self as ptype};
7
8use super::foundation::{NONSPECIFIC, Scope};
9use super::{PlanError, Textify};
10use crate::extensions::simple::{CompoundName, ExtensionKind};
11use crate::textify::foundation::{MaybeToken, Visibility};
12
13const NULLABILITY_UNSPECIFIED: &str = "⁉";
14
15impl Textify for ptype::Nullability {
16 fn name() -> &'static str {
17 "Nullability"
18 }
19
20 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
21 match self {
22 ptype::Nullability::Unspecified => {
23 ctx.push_error(
24 PlanError::invalid("Nullability", NONSPECIFIC, "Nullability left Unspecified")
25 .into(),
26 );
27
28 w.write_str(NULLABILITY_UNSPECIFIED)?;
30 }
31 ptype::Nullability::Nullable => write!(w, "?")?,
32 ptype::Nullability::Required => {}
33 };
34 Ok(())
35 }
36}
37
38pub fn is_identifer(s: &str) -> bool {
45 let mut chars = s.chars();
46 let first = match chars.next() {
47 Some(c) => c,
48 None => return false,
49 };
50
51 if !first.is_ascii_alphabetic() {
52 return false;
53 }
54
55 for c in chars {
56 if !c.is_ascii_alphanumeric() && c != '_' {
57 return false;
58 }
59 }
60
61 true
62}
63
64pub fn escaped(s: &str) -> impl fmt::Display + fmt::Debug {
66 s.escape_debug()
67}
68
69#[derive(Debug, Copy, Clone)]
72pub struct Name<'a>(pub &'a str);
73
74impl<'a> fmt::Display for Name<'a> {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if is_identifer(self.0) {
77 write!(f, "{}", self.0)
78 } else {
79 write!(f, "\"{}\"", escaped(self.0))
80 }
81 }
82}
83
84impl<'a> Textify for Name<'a> {
85 fn name() -> &'static str {
86 "Name"
87 }
88
89 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
90 write!(w, "{self}")
91 }
92}
93
94#[derive(Debug, Copy, Clone)]
95pub struct Anchor {
96 reference: u32,
97 required: bool,
98}
99
100impl Anchor {
101 pub fn new(reference: u32, required: bool) -> Self {
102 Self {
103 reference,
104 required,
105 }
106 }
107}
108
109impl Textify for Anchor {
110 fn name() -> &'static str {
111 "Anchor"
112 }
113
114 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
115 match ctx.options().show_simple_extension_anchors {
116 Visibility::Never => return Ok(()),
117 Visibility::Required if !self.required => {
118 return Ok(());
119 }
120 Visibility::Required => {}
121 Visibility::Always => {}
122 }
123 write!(w, "#{}", self.reference)
124 }
125}
126
127#[derive(Debug, Copy, Clone)]
128pub struct NamedAnchor<'a> {
129 pub name: MaybeToken<&'a CompoundName>,
131 pub anchor: u32,
132 pub unique: bool,
136 pub base_name_unique: bool,
139}
140
141impl<'a> NamedAnchor<'a> {
142 pub fn lookup<S: Scope>(ctx: &'a S, kind: ExtensionKind, anchor: u32) -> Self {
144 if kind == ExtensionKind::Function {
145 return match ctx.extensions().lookup_function(anchor) {
146 Ok(r) => Self {
147 name: MaybeToken(Ok(r.name)),
148 anchor,
149 unique: r.name_unique,
150 base_name_unique: r.base_name_unique,
151 },
152 Err(e) => Self {
153 name: MaybeToken(Err(ctx.failure(e))),
154 anchor,
155 unique: false,
156 base_name_unique: false,
157 },
158 };
159 }
160 let ext = ctx.extensions().find_by_anchor(kind, anchor);
164 let (name, unique, base_name_unique) = match ext {
165 Ok((_, n)) => {
166 let unique = match ctx.extensions().is_name_unique(kind, anchor, n.full()) {
167 Ok(u) => u,
168 Err(e) => {
169 ctx.push_error(e.into());
170 false
171 }
172 };
173 (MaybeToken(Ok(n)), unique, true)
174 }
175 Err(e) => (MaybeToken(Err(ctx.failure(e))), false, false),
176 };
177 Self {
178 name,
179 anchor,
180 unique,
181 base_name_unique,
182 }
183 }
184}
185
186impl<'a> Textify for NamedAnchor<'a> {
187 fn name() -> &'static str {
188 "NamedAnchor"
189 }
190
191 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
192 let show_signature = match ctx.options().show_simple_extension_anchors {
194 Visibility::Always => true,
195 Visibility::Required => !self.base_name_unique,
196 Visibility::Never => false,
197 };
198
199 match &self.name.0 {
200 Ok(n) => {
201 if show_signature {
202 write!(w, "{}", n.full())?;
203 } else {
204 write!(w, "{}", n.base())?;
205 }
206 }
207 Err(e) => write!(w, "{e}")?,
208 }
209
210 let needs_anchor = !self.unique
216 || match &self.name.0 {
217 Ok(n) if !n.full().contains(':') => !self.base_name_unique,
218 _ => false,
219 };
220 let anchor = Anchor::new(self.anchor, needs_anchor);
221 write!(w, "{}", ctx.display(&anchor))
222 }
223}
224
225#[derive(Debug, Copy, Clone)]
227pub struct OutputType<T: Deref<Target = proto::Type>>(pub Option<T>);
228
229impl<T: Deref<Target = proto::Type>> Textify for OutputType<T> {
230 fn name() -> &'static str {
231 "OutputType"
232 }
233
234 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
235 match self.0 {
236 Some(ref t) => write!(w, ":{}", ctx.display(t.deref())),
237 None => write!(
238 w,
239 "{}",
240 ctx.failure(PlanError::invalid(
241 "OutputType",
242 None::<&str>,
243 "function output_type must be set",
244 ))
245 ),
246 }
247 }
248}
249
250struct TypeVariation(u32);
251
252impl Textify for TypeVariation {
253 fn name() -> &'static str {
254 "TypeVariation"
255 }
256
257 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
258 let &TypeVariation(anchor) = self;
259 if anchor == 0 {
260 return Ok(());
262 }
263 let name_and_anchor = NamedAnchor::lookup(ctx, ExtensionKind::TypeVariation, anchor);
264
265 write!(
266 w,
267 "[{name_and_anchor}]",
268 name_and_anchor = ctx.display(&name_and_anchor)
269 )
270 }
271}
272
273fn textify_type<S: Scope, W: fmt::Write>(
278 ctx: &S,
279 f: &mut W,
280 name: impl AsRef<str>,
281 nullability: ptype::Nullability,
282 variant: u32,
283 params: Parameters,
284) -> fmt::Result {
285 write!(
286 f,
287 "{name}{null}{var}{params}",
288 name = name.as_ref(),
289 null = ctx.display(&nullability),
290 var = ctx.display(&TypeVariation(variant)),
291 params = ctx.display(¶ms)
292 )
293}
294
295macro_rules! textify_kind {
296 ($ctx:expr, $f:expr, $kind:ident, $name:expr) => {
297 textify_type(
298 $ctx,
299 $f,
300 $name,
301 $kind.nullability(),
302 $kind.type_variation_reference,
303 Parameters(&[]),
304 )
305 };
306}
307
308impl Textify for Parameter {
309 fn name() -> &'static str {
310 "Parameter"
311 }
312
313 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
314 match self {
315 Parameter::Boolean(true) => write!(w, "true")?,
316 Parameter::Boolean(false) => write!(w, "false")?,
317 Parameter::DataType(t) => write!(w, "{}", ctx.display(t))?,
318 Parameter::Enum(e) => write!(w, "{e}")?,
319 Parameter::Integer(i) => write!(w, "{i}")?,
320 Parameter::String(s) => write!(w, "{s}")?,
322 Parameter::Null(_) => write!(w, "null")?,
323 };
324
325 Ok(())
326 }
327}
328impl Textify for ptype::Parameter {
329 fn name() -> &'static str {
330 "Parameter"
331 }
332
333 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
334 write!(w, "{}", ctx.expect(self.parameter.as_ref()))
335 }
336}
337
338struct Parameters<'a>(&'a [Option<Parameter>]);
339
340impl<'a> Textify for Parameters<'a> {
341 fn name() -> &'static str {
342 "Parameters"
343 }
344
345 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
346 let mut first = true;
347 for param in self.0.iter() {
348 if first {
349 write!(w, "<")?;
350 } else {
351 write!(w, ", ")?;
352 }
353 write!(w, "{}", ctx.expect(param.as_ref()))?;
354 first = false;
355 }
356 if !first {
357 write!(w, ">")?;
358 }
359
360 Ok(())
361 }
362}
363
364impl Textify for ptype::UserDefined {
365 fn name() -> &'static str {
366 "UserDefined"
367 }
368
369 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
370 {
371 let name_and_anchor =
372 NamedAnchor::lookup(ctx, ExtensionKind::Type, self.type_reference);
373
374 let param_vec: Vec<Option<Parameter>> = self
375 .type_parameters
376 .iter()
377 .map(|t| t.parameter.clone())
378 .collect();
379 let params = Parameters(¶m_vec);
380
381 write!(
382 w,
383 "{name_and_anchor}{null}{var}{params}",
384 name_and_anchor = ctx.display(&name_and_anchor),
385 null = ctx.display(&self.nullability()),
386 var = ctx.display(&TypeVariation(self.type_variation_reference)),
387 params = ctx.display(¶ms)
388 )
389 }
390 }
391}
392
393impl Textify for ptype::Kind {
394 fn name() -> &'static str {
395 "Kind"
396 }
397
398 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
399 match self {
400 ptype::Kind::Bool(k) => textify_type(
404 ctx,
405 w,
406 "boolean",
407 k.nullability(),
408 k.type_variation_reference,
409 Parameters(&[]),
410 ),
411 ptype::Kind::I8(k) => textify_kind!(ctx, w, k, "i8"),
412 ptype::Kind::I16(k) => textify_kind!(ctx, w, k, "i16"),
413 ptype::Kind::I32(k) => textify_kind!(ctx, w, k, "i32"),
414 ptype::Kind::I64(k) => textify_kind!(ctx, w, k, "i64"),
415 ptype::Kind::Fp32(k) => textify_kind!(ctx, w, k, "fp32"),
416 ptype::Kind::Fp64(k) => textify_kind!(ctx, w, k, "fp64"),
417 ptype::Kind::String(k) => textify_kind!(ctx, w, k, "string"),
418 ptype::Kind::Binary(k) => textify_kind!(ctx, w, k, "binary"),
419 #[allow(deprecated)]
420 ptype::Kind::Timestamp(k) => textify_kind!(ctx, w, k, "timestamp"),
421 ptype::Kind::Date(k) => textify_kind!(ctx, w, k, "date"),
422 #[allow(deprecated)]
423 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
424 ptype::Kind::IntervalYear(i) => {
425 textify_kind!(ctx, w, i, "interval_year")
426 }
427 #[allow(deprecated)]
428 ptype::Kind::TimestampTz(ts) => {
429 textify_kind!(ctx, w, ts, "timestamp_tz")
430 }
431 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
432
433 ptype::Kind::IntervalDay(i) => textify_type(
434 ctx,
435 w,
436 "interval_day",
437 i.nullability(),
438 i.type_variation_reference,
439 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
441 ),
442 ptype::Kind::IntervalCompound(i) => textify_type(
443 ctx,
444 w,
445 "interval_compound",
446 i.nullability(),
447 i.type_variation_reference,
448 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
449 ),
450 ptype::Kind::FixedChar(c) => textify_type(
451 ctx,
452 w,
453 "fixedchar",
454 c.nullability(),
455 c.type_variation_reference,
456 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
457 ),
458 ptype::Kind::Varchar(c) => textify_type(
459 ctx,
460 w,
461 "varchar",
462 c.nullability(),
463 c.type_variation_reference,
464 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
465 ),
466 ptype::Kind::FixedBinary(b) => textify_type(
467 ctx,
468 w,
469 "fixedbinary",
470 b.nullability(),
471 b.type_variation_reference,
472 Parameters(&[Some(Parameter::Integer(b.length as i64))]),
473 ),
474 ptype::Kind::Decimal(d) => {
475 let p = Parameter::Integer(d.precision as i64);
476 let s = Parameter::Integer(d.scale as i64);
477 textify_type(
478 ctx,
479 w,
480 "decimal",
481 d.nullability(),
482 d.type_variation_reference,
483 Parameters(&[Some(p), Some(s)]),
484 )
485 }
486 ptype::Kind::PrecisionTime(p) => textify_type(
487 ctx,
488 w,
489 "precisiontime",
490 p.nullability(),
491 p.type_variation_reference,
492 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
493 ),
494 ptype::Kind::PrecisionTimestamp(p) => textify_type(
495 ctx,
496 w,
497 "precisiontimestamp",
498 p.nullability(),
499 p.type_variation_reference,
500 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
501 ),
502 ptype::Kind::PrecisionTimestampTz(p) => textify_type(
503 ctx,
504 w,
505 "precisiontimestamptz",
506 p.nullability(),
507 p.type_variation_reference,
508 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
509 ),
510 ptype::Kind::Struct(s) => textify_type(
511 ctx,
512 w,
513 "struct",
514 s.nullability(),
515 s.type_variation_reference,
516 Parameters(
517 &s.types
518 .iter()
519 .map(|t| Some(Parameter::DataType(t.clone())))
520 .collect::<Vec<_>>(),
521 ),
522 ),
523 ptype::Kind::List(l) => {
524 let p = l
525 .r#type
526 .as_ref()
527 .map(|t| Parameter::DataType((**t).to_owned()));
528 textify_type(
529 ctx,
530 w,
531 "list",
532 l.nullability(),
533 l.type_variation_reference,
534 Parameters(&[p]),
535 )
536 }
537 ptype::Kind::Map(m) => {
538 let k = m
539 .key
540 .as_ref()
541 .map(|t| Parameter::DataType((**t).to_owned()));
542 let v = m
543 .value
544 .as_ref()
545 .map(|t| Parameter::DataType((**t).to_owned()));
546 textify_type(
547 ctx,
548 w,
549 "map",
550 m.nullability(),
551 m.type_variation_reference,
552 Parameters(&[k, v]),
553 )
554 }
555 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
556 #[allow(deprecated)]
557 ptype::Kind::UserDefinedTypeReference(r) => {
558 let udf = ptype::UserDefined {
561 type_reference: *r,
562 type_variation_reference: 0,
563 nullability: ptype::Nullability::Required as i32,
564 type_parameters: vec![],
565 };
566 ptype::Kind::UserDefined(udf).textify(ctx, w)
567 }
568 ptype::Kind::Func(_f) => {
569 write!(
570 w,
571 "{}",
572 ctx.failure(PlanError::unimplemented(
573 "FuncType",
574 Some("Func"),
575 "Function type textification not implemented",
576 ))
577 )
578 }
579 ptype::Kind::Alias(_p) => {
580 write!(
581 w,
582 "{}",
583 ctx.failure(PlanError::unimplemented(
584 "AliasType",
585 Some("Alias"),
586 "TypeAliasReference textification not implemented",
587 ))
588 )
589 }
590 }
591 }
592}
593
594impl Textify for proto::Type {
595 fn name() -> &'static str {
596 "Type"
597 }
598
599 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
600 write!(w, "{}", ctx.expect(self.kind.as_ref()))
601 }
602}
603
604#[cfg(test)]
700mod tests {
701
702 use super::*;
703 use crate::extensions::simple::{ExtensionKind, MissingReference};
704 use crate::fixtures::TestContext;
705 use crate::textify::foundation::FormatError;
706
707 #[test]
708 fn type_display() {
709 let ctx = TestContext::new()
710 .with_urn(1, "first")
711 .with_type_variation(1, 2, "u8");
712
713 let t = proto::Type {
714 kind: Some(ptype::Kind::Bool(ptype::Boolean {
715 type_variation_reference: 2,
716 nullability: ptype::Nullability::Nullable as i32,
717 })),
718 };
719
720 let s = ctx.textify_no_errors(&t);
721 assert_eq!(s, "boolean?[u8]");
722
723 let t = proto::Type {
724 kind: Some(ptype::Kind::I8(ptype::I8 {
725 type_variation_reference: 0,
726 nullability: ptype::Nullability::Required as i32,
727 })),
728 };
729 assert_eq!(ctx.textify_no_errors(&t), "i8");
730
731 let t = proto::Type {
732 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
733 type_variation_reference: 0,
734 nullability: ptype::Nullability::Nullable as i32,
735 precision: 3,
736 })),
737 };
738 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
739
740 let mut ctx = ctx.with_type_variation(1, 8, "int");
741 ctx.options.show_simple_extension_anchors = Visibility::Always;
742
743 let t = proto::Type {
744 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
745 type_variation_reference: 8,
746 nullability: ptype::Nullability::Nullable as i32,
747 precision: 9,
748 })),
749 };
750 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
751 }
752
753 #[test]
754 fn type_display_with_errors() {
755 let ctx = TestContext::new()
756 .with_urn(1, "first")
757 .with_type(1, 100, "cow");
758
759 let t = proto::Type {
760 kind: Some(ptype::Kind::Bool(ptype::Boolean {
761 type_variation_reference: 200,
762 nullability: ptype::Nullability::Nullable as i32,
763 })),
764 };
765 let (s, errs) = ctx.textify(&t);
766 assert_eq!(s, "boolean?[!{type_variation}#200]");
767 let err = errs.first();
768 let (&k, &a) = match err {
769 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
770 _ => panic!("Expected Lookup MissingAnchor: {err}"),
771 };
772
773 assert_eq!(k, ExtensionKind::TypeVariation);
774 assert_eq!(a, 200);
775
776 let t = proto::Type {
777 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
778 type_variation_reference: 0,
779 nullability: ptype::Nullability::Required as i32,
780 type_reference: 100,
781 type_parameters: vec![],
782 })),
783 };
784
785 let (s, errs) = ctx.textify(&t);
786 assert!(errs.is_empty());
787 assert_eq!(s, "cow");
788
789 let t = proto::Type {
790 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
791 type_variation_reference: 0,
792 nullability: ptype::Nullability::Required as i32,
793 type_reference: 12589,
794 type_parameters: vec![],
795 })),
796 };
797
798 let (s, errs) = ctx.textify(&t);
799 let err = errs.first();
800 let (&k, &a) = match err {
801 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
802 _ => panic!("Expected Lookup MissingAnchor: {err}"),
803 };
804 assert_eq!(k, ExtensionKind::Type);
805 assert_eq!(a, 12589);
806 assert_eq!(s, "!{type}#12589");
807 }
808
809 #[test]
810 fn struct_display() {
811 let ctx = TestContext::new();
812 let t = proto::Type {
813 kind: Some(ptype::Kind::Struct(ptype::Struct {
814 type_variation_reference: 0,
815 nullability: ptype::Nullability::Nullable as i32,
816 types: vec![
817 proto::Type {
818 kind: Some(ptype::Kind::String(ptype::String {
819 type_variation_reference: 0,
820 nullability: ptype::Nullability::Required as i32,
821 })),
822 },
823 proto::Type {
824 kind: Some(ptype::Kind::I8(ptype::I8 {
825 type_variation_reference: 0,
826 nullability: ptype::Nullability::Required as i32,
827 })),
828 },
829 proto::Type {
830 kind: Some(ptype::Kind::I32(ptype::I32 {
831 type_variation_reference: 0,
832 nullability: ptype::Nullability::Nullable as i32,
833 })),
834 },
835 proto::Type {
836 #[allow(deprecated)] kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
838 type_variation_reference: 0,
839 nullability: ptype::Nullability::Required as i32,
840 })),
841 },
842 ],
843 })),
844 };
845 assert_eq!(
846 ctx.textify_no_errors(&t),
847 "struct?<string, i8, i32?, timestamp_tz>"
848 );
849 }
850
851 #[test]
852 fn names_display() {
853 let ctx = TestContext::new();
854
855 let n = Name("name");
856 assert_eq!(ctx.textify_no_errors(&n), "name");
857
858 let n = Name("name with spaces");
859 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
860 }
861
862 fn overloaded_ctx() -> TestContext {
959 TestContext::new()
960 .with_urn(1, "substrait:functions_comparison")
961 .with_function(1, 1, "equal:any_any")
962 .with_function(1, 2, "equal:str_str")
963 .with_function(1, 3, "add:i64_i64")
964 }
965
966 #[test]
967 fn named_anchor_compact_unique_base_name_no_signature() {
968 let ctx = overloaded_ctx();
971 let eq = crate::textify::ErrorQueue::default();
972 let scope = ctx.scope(&eq);
973 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
974 assert!(na.base_name_unique, "add should have unique base name");
975 assert!(na.unique, "add:i64_i64 compound name should be unique");
976
977 let s = ctx.textify_no_errors(&na);
978 assert_eq!(s, "add");
979 }
980
981 #[test]
982 fn named_anchor_compact_overloaded_shows_signature() {
983 let ctx = overloaded_ctx();
987 let eq = crate::textify::ErrorQueue::default();
988 let scope = ctx.scope(&eq);
989 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
990 assert!(!na.base_name_unique, "equal base name should not be unique");
991 assert!(
992 na.unique,
993 "equal:any_any compound name should be unique across URNs"
994 );
995
996 let s = ctx.textify_no_errors(&na);
997 assert_eq!(s, "equal:any_any");
998 }
999
1000 #[test]
1001 fn named_anchor_verbose_unique_base_name_shows_signature_and_anchor() {
1002 let mut ctx = overloaded_ctx();
1004 ctx.options.show_simple_extension_anchors = Visibility::Always;
1005
1006 let eq = crate::textify::ErrorQueue::default();
1007 let scope = ctx.scope(&eq);
1008 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
1009 let s = ctx.textify_no_errors(&na);
1010 assert_eq!(s, "add:i64_i64#3");
1011 }
1012
1013 #[test]
1014 fn named_anchor_verbose_overloaded_shows_signature_and_anchor() {
1015 let mut ctx = overloaded_ctx();
1017 ctx.options.show_simple_extension_anchors = Visibility::Always;
1018
1019 let eq = crate::textify::ErrorQueue::default();
1020 let scope = ctx.scope(&eq);
1021 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 2);
1022 let s = ctx.textify_no_errors(&na);
1023 assert_eq!(s, "equal:str_str#2");
1024 }
1025
1026 #[test]
1027 fn named_anchor_compact_same_compound_name_two_urns_shows_anchor() {
1028 let ctx = TestContext::new()
1031 .with_urn(1, "urn_a")
1032 .with_urn(2, "urn_b")
1033 .with_function(1, 1, "equal:any_any")
1034 .with_function(2, 2, "equal:any_any");
1035
1036 let eq = crate::textify::ErrorQueue::default();
1037 let scope = ctx.scope(&eq);
1038 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
1039 assert!(!na.base_name_unique);
1040 assert!(!na.unique, "compound name not unique across two URNs");
1041
1042 let s = ctx.textify_no_errors(&na);
1043 assert_eq!(s, "equal:any_any#1");
1044 }
1045
1046 #[test]
1047 fn named_anchor_compact_unique_full_name_zero_arg_type_signature() {
1048 let ctx = TestContext::new()
1053 .with_urn(1, "urn")
1054 .with_function(1, 5, "count:");
1055
1056 let eq = crate::textify::ErrorQueue::default();
1057 let scope = ctx.scope(&eq);
1058 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 5);
1059 assert!(na.base_name_unique);
1060 assert!(na.unique);
1061
1062 let s = ctx.textify_no_errors(&na);
1063 assert_eq!(s, "count");
1064 }
1065
1066 #[test]
1067 fn named_anchor_verbose_full_name_shows_signature_and_anchor() {
1068 let mut ctx = TestContext::new()
1070 .with_urn(1, "urn")
1071 .with_function(1, 5, "count:");
1072 ctx.options.show_simple_extension_anchors = Visibility::Always;
1073
1074 let eq = crate::textify::ErrorQueue::default();
1075 let scope = ctx.scope(&eq);
1076 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 5);
1077
1078 let s = ctx.textify_no_errors(&na);
1079 assert_eq!(s, "count:#5");
1080 }
1081
1082 #[test]
1083 fn named_anchor_compact_plain_name_unique_no_signature_no_anchor() {
1084 let ctx = TestContext::new()
1085 .with_urn(1, "urn")
1086 .with_function(1, 10, "coalesce");
1087
1088 let eq = crate::textify::ErrorQueue::default();
1089 let scope = ctx.scope(&eq);
1090 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 10);
1091 assert!(na.base_name_unique);
1092 assert!(na.unique);
1093
1094 let s = ctx.textify_no_errors(&na);
1095 assert_eq!(s, "coalesce");
1096 }
1097
1098 #[test]
1099 fn named_anchor_compact_plain_name_non_unique_shows_anchor() {
1100 let ctx = TestContext::new()
1101 .with_urn(1, "urn1")
1102 .with_urn(2, "urn2")
1103 .with_function(1, 231, "duplicated")
1104 .with_function(2, 232, "duplicated");
1105
1106 let eq = crate::textify::ErrorQueue::default();
1107 let scope = ctx.scope(&eq);
1108 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 231);
1109 assert!(!na.base_name_unique);
1110 assert!(!na.unique);
1111
1112 let s = ctx.textify_no_errors(&na);
1113 assert_eq!(s, "duplicated#231");
1114 }
1115}