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 anchor = Anchor::new(self.anchor, !self.unique);
211 write!(w, "{}", ctx.display(&anchor))
212 }
213}
214
215#[derive(Debug, Copy, Clone)]
220pub struct OutputType<T: Deref<Target = proto::Type>>(pub Option<T>);
221
222impl<T: Deref<Target = proto::Type>> Textify for OutputType<T> {
223 fn name() -> &'static str {
224 "OutputType"
225 }
226
227 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
228 match self.0 {
229 Some(ref t) => write!(w, ":{}", ctx.display(t.deref())),
230 None => Ok(()),
231 }
232 }
233}
234
235struct TypeVariation(u32);
236
237impl Textify for TypeVariation {
238 fn name() -> &'static str {
239 "TypeVariation"
240 }
241
242 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
243 let &TypeVariation(anchor) = self;
244 if anchor == 0 {
245 return Ok(());
247 }
248 let name_and_anchor = NamedAnchor::lookup(ctx, ExtensionKind::TypeVariation, anchor);
249
250 write!(
251 w,
252 "[{name_and_anchor}]",
253 name_and_anchor = ctx.display(&name_and_anchor)
254 )
255 }
256}
257
258fn textify_type<S: Scope, W: fmt::Write>(
263 ctx: &S,
264 f: &mut W,
265 name: impl AsRef<str>,
266 nullability: ptype::Nullability,
267 variant: u32,
268 params: Parameters,
269) -> fmt::Result {
270 write!(
271 f,
272 "{name}{null}{var}{params}",
273 name = name.as_ref(),
274 null = ctx.display(&nullability),
275 var = ctx.display(&TypeVariation(variant)),
276 params = ctx.display(¶ms)
277 )
278}
279
280macro_rules! textify_kind {
281 ($ctx:expr, $f:expr, $kind:ident, $name:expr) => {
282 textify_type(
283 $ctx,
284 $f,
285 $name,
286 $kind.nullability(),
287 $kind.type_variation_reference,
288 Parameters(&[]),
289 )
290 };
291}
292
293impl Textify for Parameter {
294 fn name() -> &'static str {
295 "Parameter"
296 }
297
298 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
299 match self {
300 Parameter::Boolean(true) => write!(w, "true")?,
301 Parameter::Boolean(false) => write!(w, "false")?,
302 Parameter::DataType(t) => write!(w, "{}", ctx.display(t))?,
303 Parameter::Enum(e) => write!(w, "{e}")?,
304 Parameter::Integer(i) => write!(w, "{i}")?,
305 Parameter::String(s) => write!(w, "{s}")?,
307 Parameter::Null(_) => write!(w, "null")?,
308 };
309
310 Ok(())
311 }
312}
313impl Textify for ptype::Parameter {
314 fn name() -> &'static str {
315 "Parameter"
316 }
317
318 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
319 write!(w, "{}", ctx.expect(self.parameter.as_ref()))
320 }
321}
322
323struct Parameters<'a>(&'a [Option<Parameter>]);
324
325impl<'a> Textify for Parameters<'a> {
326 fn name() -> &'static str {
327 "Parameters"
328 }
329
330 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
331 let mut first = true;
332 for param in self.0.iter() {
333 if first {
334 write!(w, "<")?;
335 } else {
336 write!(w, ", ")?;
337 }
338 write!(w, "{}", ctx.expect(param.as_ref()))?;
339 first = false;
340 }
341 if !first {
342 write!(w, ">")?;
343 }
344
345 Ok(())
346 }
347}
348
349impl Textify for ptype::UserDefined {
350 fn name() -> &'static str {
351 "UserDefined"
352 }
353
354 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
355 {
356 let name_and_anchor =
357 NamedAnchor::lookup(ctx, ExtensionKind::Type, self.type_reference);
358
359 let param_vec: Vec<Option<Parameter>> = self
360 .type_parameters
361 .iter()
362 .map(|t| t.parameter.clone())
363 .collect();
364 let params = Parameters(¶m_vec);
365
366 write!(
367 w,
368 "{name_and_anchor}{null}{var}{params}",
369 name_and_anchor = ctx.display(&name_and_anchor),
370 null = ctx.display(&self.nullability()),
371 var = ctx.display(&TypeVariation(self.type_variation_reference)),
372 params = ctx.display(¶ms)
373 )
374 }
375 }
376}
377
378impl Textify for ptype::Kind {
379 fn name() -> &'static str {
380 "Kind"
381 }
382
383 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
384 match self {
385 ptype::Kind::Bool(k) => textify_type(
389 ctx,
390 w,
391 "boolean",
392 k.nullability(),
393 k.type_variation_reference,
394 Parameters(&[]),
395 ),
396 ptype::Kind::I8(k) => textify_kind!(ctx, w, k, "i8"),
397 ptype::Kind::I16(k) => textify_kind!(ctx, w, k, "i16"),
398 ptype::Kind::I32(k) => textify_kind!(ctx, w, k, "i32"),
399 ptype::Kind::I64(k) => textify_kind!(ctx, w, k, "i64"),
400 ptype::Kind::Fp32(k) => textify_kind!(ctx, w, k, "fp32"),
401 ptype::Kind::Fp64(k) => textify_kind!(ctx, w, k, "fp64"),
402 ptype::Kind::String(k) => textify_kind!(ctx, w, k, "string"),
403 ptype::Kind::Binary(k) => textify_kind!(ctx, w, k, "binary"),
404 #[allow(deprecated)]
405 ptype::Kind::Timestamp(k) => textify_kind!(ctx, w, k, "timestamp"),
406 ptype::Kind::Date(k) => textify_kind!(ctx, w, k, "date"),
407 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
408 ptype::Kind::IntervalYear(i) => {
409 textify_kind!(ctx, w, i, "interval_year")
410 }
411 #[allow(deprecated)]
412 ptype::Kind::TimestampTz(ts) => {
413 textify_kind!(ctx, w, ts, "timestamp_tz")
414 }
415 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
416
417 ptype::Kind::IntervalDay(i) => textify_type(
418 ctx,
419 w,
420 "interval_day",
421 i.nullability(),
422 i.type_variation_reference,
423 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
425 ),
426 ptype::Kind::IntervalCompound(i) => textify_type(
427 ctx,
428 w,
429 "interval_compound",
430 i.nullability(),
431 i.type_variation_reference,
432 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
433 ),
434 ptype::Kind::FixedChar(c) => textify_type(
435 ctx,
436 w,
437 "fixedchar",
438 c.nullability(),
439 c.type_variation_reference,
440 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
441 ),
442 ptype::Kind::Varchar(c) => textify_type(
443 ctx,
444 w,
445 "varchar",
446 c.nullability(),
447 c.type_variation_reference,
448 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
449 ),
450 ptype::Kind::FixedBinary(b) => textify_type(
451 ctx,
452 w,
453 "fixedbinary",
454 b.nullability(),
455 b.type_variation_reference,
456 Parameters(&[Some(Parameter::Integer(b.length as i64))]),
457 ),
458 ptype::Kind::Decimal(d) => {
459 let p = Parameter::Integer(d.precision as i64);
460 let s = Parameter::Integer(d.scale as i64);
461 textify_type(
462 ctx,
463 w,
464 "decimal",
465 d.nullability(),
466 d.type_variation_reference,
467 Parameters(&[Some(p), Some(s)]),
468 )
469 }
470 ptype::Kind::PrecisionTime(p) => textify_type(
471 ctx,
472 w,
473 "precisiontime",
474 p.nullability(),
475 p.type_variation_reference,
476 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
477 ),
478 ptype::Kind::PrecisionTimestamp(p) => textify_type(
479 ctx,
480 w,
481 "precisiontimestamp",
482 p.nullability(),
483 p.type_variation_reference,
484 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
485 ),
486 ptype::Kind::PrecisionTimestampTz(p) => textify_type(
487 ctx,
488 w,
489 "precisiontimestamptz",
490 p.nullability(),
491 p.type_variation_reference,
492 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
493 ),
494 ptype::Kind::Struct(s) => textify_type(
495 ctx,
496 w,
497 "struct",
498 s.nullability(),
499 s.type_variation_reference,
500 Parameters(
501 &s.types
502 .iter()
503 .map(|t| Some(Parameter::DataType(t.clone())))
504 .collect::<Vec<_>>(),
505 ),
506 ),
507 ptype::Kind::List(l) => {
508 let p = l
509 .r#type
510 .as_ref()
511 .map(|t| Parameter::DataType((**t).to_owned()));
512 textify_type(
513 ctx,
514 w,
515 "list",
516 l.nullability(),
517 l.type_variation_reference,
518 Parameters(&[p]),
519 )
520 }
521 ptype::Kind::Map(m) => {
522 let k = m
523 .key
524 .as_ref()
525 .map(|t| Parameter::DataType((**t).to_owned()));
526 let v = m
527 .value
528 .as_ref()
529 .map(|t| Parameter::DataType((**t).to_owned()));
530 textify_type(
531 ctx,
532 w,
533 "map",
534 m.nullability(),
535 m.type_variation_reference,
536 Parameters(&[k, v]),
537 )
538 }
539 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
540 #[allow(deprecated)]
541 ptype::Kind::UserDefinedTypeReference(r) => {
542 let udf = ptype::UserDefined {
545 type_reference: *r,
546 type_variation_reference: 0,
547 nullability: ptype::Nullability::Required as i32,
548 type_parameters: vec![],
549 };
550 ptype::Kind::UserDefined(udf).textify(ctx, w)
551 }
552 ptype::Kind::Alias(_p) => {
553 write!(
554 w,
555 "{}",
556 ctx.failure(PlanError::unimplemented(
557 "AliasType",
558 Some("Alias"),
559 "TypeAliasReference textification not implemented",
560 ))
561 )
562 }
563 }
564 }
565}
566
567impl Textify for proto::Type {
568 fn name() -> &'static str {
569 "Type"
570 }
571
572 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
573 write!(w, "{}", ctx.expect(self.kind.as_ref()))
574 }
575}
576
577#[cfg(test)]
673mod tests {
674
675 use super::*;
676 use crate::extensions::simple::{ExtensionKind, MissingReference};
677 use crate::fixtures::TestContext;
678 use crate::textify::foundation::FormatError;
679
680 #[test]
681 fn type_display() {
682 let ctx = TestContext::new()
683 .with_urn(1, "first")
684 .with_type_variation(1, 2, "u8");
685
686 let t = proto::Type {
687 kind: Some(ptype::Kind::Bool(ptype::Boolean {
688 type_variation_reference: 2,
689 nullability: ptype::Nullability::Nullable as i32,
690 })),
691 };
692
693 let s = ctx.textify_no_errors(&t);
694 assert_eq!(s, "boolean?[u8]");
695
696 let t = proto::Type {
697 kind: Some(ptype::Kind::I8(ptype::I8 {
698 type_variation_reference: 0,
699 nullability: ptype::Nullability::Required as i32,
700 })),
701 };
702 assert_eq!(ctx.textify_no_errors(&t), "i8");
703
704 let t = proto::Type {
705 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
706 type_variation_reference: 0,
707 nullability: ptype::Nullability::Nullable as i32,
708 precision: 3,
709 })),
710 };
711 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
712
713 let mut ctx = ctx.with_type_variation(1, 8, "int");
714 ctx.options.show_simple_extension_anchors = Visibility::Always;
715
716 let t = proto::Type {
717 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
718 type_variation_reference: 8,
719 nullability: ptype::Nullability::Nullable as i32,
720 precision: 9,
721 })),
722 };
723 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
724 }
725
726 #[test]
727 fn type_display_with_errors() {
728 let ctx = TestContext::new()
729 .with_urn(1, "first")
730 .with_type(1, 100, "cow");
731
732 let t = proto::Type {
733 kind: Some(ptype::Kind::Bool(ptype::Boolean {
734 type_variation_reference: 200,
735 nullability: ptype::Nullability::Nullable as i32,
736 })),
737 };
738 let (s, errs) = ctx.textify(&t);
739 assert_eq!(s, "boolean?[!{type_variation}#200]");
740 let err = errs.first();
741 let (&k, &a) = match err {
742 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
743 _ => panic!("Expected Lookup MissingAnchor: {err}"),
744 };
745
746 assert_eq!(k, ExtensionKind::TypeVariation);
747 assert_eq!(a, 200);
748
749 let t = proto::Type {
750 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
751 type_variation_reference: 0,
752 nullability: ptype::Nullability::Required as i32,
753 type_reference: 100,
754 type_parameters: vec![],
755 })),
756 };
757
758 let (s, errs) = ctx.textify(&t);
759 assert!(errs.is_empty());
760 assert_eq!(s, "cow");
761
762 let t = proto::Type {
763 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
764 type_variation_reference: 0,
765 nullability: ptype::Nullability::Required as i32,
766 type_reference: 12589,
767 type_parameters: vec![],
768 })),
769 };
770
771 let (s, errs) = ctx.textify(&t);
772 let err = errs.first();
773 let (&k, &a) = match err {
774 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
775 _ => panic!("Expected Lookup MissingAnchor: {err}"),
776 };
777 assert_eq!(k, ExtensionKind::Type);
778 assert_eq!(a, 12589);
779 assert_eq!(s, "!{type}#12589");
780 }
781
782 #[test]
783 fn struct_display() {
784 let ctx = TestContext::new();
785 let t = proto::Type {
786 kind: Some(ptype::Kind::Struct(ptype::Struct {
787 type_variation_reference: 0,
788 nullability: ptype::Nullability::Nullable as i32,
789 types: vec![
790 proto::Type {
791 kind: Some(ptype::Kind::String(ptype::String {
792 type_variation_reference: 0,
793 nullability: ptype::Nullability::Required as i32,
794 })),
795 },
796 proto::Type {
797 kind: Some(ptype::Kind::I8(ptype::I8 {
798 type_variation_reference: 0,
799 nullability: ptype::Nullability::Required as i32,
800 })),
801 },
802 proto::Type {
803 kind: Some(ptype::Kind::I32(ptype::I32 {
804 type_variation_reference: 0,
805 nullability: ptype::Nullability::Nullable as i32,
806 })),
807 },
808 proto::Type {
809 #[allow(deprecated)] kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
811 type_variation_reference: 0,
812 nullability: ptype::Nullability::Required as i32,
813 })),
814 },
815 ],
816 })),
817 };
818 assert_eq!(
819 ctx.textify_no_errors(&t),
820 "struct?<string, i8, i32?, timestamp_tz>"
821 );
822 }
823
824 #[test]
825 fn names_display() {
826 let ctx = TestContext::new();
827
828 let n = Name("name");
829 assert_eq!(ctx.textify_no_errors(&n), "name");
830
831 let n = Name("name with spaces");
832 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
833 }
834
835 fn overloaded_ctx() -> TestContext {
932 TestContext::new()
933 .with_urn(1, "substrait:functions_comparison")
934 .with_function(1, 1, "equal:any_any")
935 .with_function(1, 2, "equal:str_str")
936 .with_function(1, 3, "add:i64_i64")
937 }
938
939 #[test]
940 fn named_anchor_compact_unique_base_name_no_signature() {
941 let ctx = overloaded_ctx();
944 let eq = crate::textify::ErrorQueue::default();
945 let scope = ctx.scope(&eq);
946 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
947 assert!(na.base_name_unique, "add should have unique base name");
948 assert!(na.unique, "add:i64_i64 compound name should be unique");
949
950 let s = ctx.textify_no_errors(&na);
951 assert_eq!(s, "add");
952 }
953
954 #[test]
955 fn named_anchor_compact_overloaded_shows_signature() {
956 let ctx = overloaded_ctx();
960 let eq = crate::textify::ErrorQueue::default();
961 let scope = ctx.scope(&eq);
962 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
963 assert!(!na.base_name_unique, "equal base name should not be unique");
964 assert!(
965 na.unique,
966 "equal:any_any compound name should be unique across URNs"
967 );
968
969 let s = ctx.textify_no_errors(&na);
970 assert_eq!(s, "equal:any_any");
971 }
972
973 #[test]
974 fn named_anchor_verbose_unique_base_name_shows_signature_and_anchor() {
975 let mut ctx = overloaded_ctx();
977 ctx.options.show_simple_extension_anchors = Visibility::Always;
978
979 let eq = crate::textify::ErrorQueue::default();
980 let scope = ctx.scope(&eq);
981 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
982 let s = ctx.textify_no_errors(&na);
983 assert_eq!(s, "add:i64_i64#3");
984 }
985
986 #[test]
987 fn named_anchor_verbose_overloaded_shows_signature_and_anchor() {
988 let mut ctx = overloaded_ctx();
990 ctx.options.show_simple_extension_anchors = Visibility::Always;
991
992 let eq = crate::textify::ErrorQueue::default();
993 let scope = ctx.scope(&eq);
994 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 2);
995 let s = ctx.textify_no_errors(&na);
996 assert_eq!(s, "equal:str_str#2");
997 }
998
999 #[test]
1000 fn named_anchor_compact_same_compound_name_two_urns_shows_anchor() {
1001 let ctx = TestContext::new()
1004 .with_urn(1, "urn_a")
1005 .with_urn(2, "urn_b")
1006 .with_function(1, 1, "equal:any_any")
1007 .with_function(2, 2, "equal:any_any");
1008
1009 let eq = crate::textify::ErrorQueue::default();
1010 let scope = ctx.scope(&eq);
1011 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
1012 assert!(!na.base_name_unique);
1013 assert!(!na.unique, "compound name not unique across two URNs");
1014
1015 let s = ctx.textify_no_errors(&na);
1016 assert_eq!(s, "equal:any_any#1");
1017 }
1018
1019 #[test]
1020 fn named_anchor_compact_plain_name_unique_no_signature_no_anchor() {
1021 let ctx = TestContext::new()
1022 .with_urn(1, "urn")
1023 .with_function(1, 10, "coalesce");
1024
1025 let eq = crate::textify::ErrorQueue::default();
1026 let scope = ctx.scope(&eq);
1027 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 10);
1028 assert!(na.base_name_unique);
1029 assert!(na.unique);
1030
1031 let s = ctx.textify_no_errors(&na);
1032 assert_eq!(s, "coalesce");
1033 }
1034
1035 #[test]
1036 fn named_anchor_compact_plain_name_non_unique_shows_anchor() {
1037 let ctx = TestContext::new()
1038 .with_urn(1, "urn1")
1039 .with_urn(2, "urn2")
1040 .with_function(1, 231, "duplicated")
1041 .with_function(2, 232, "duplicated");
1042
1043 let eq = crate::textify::ErrorQueue::default();
1044 let scope = ctx.scope(&eq);
1045 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 231);
1046 assert!(!na.base_name_unique);
1047 assert!(!na.unique);
1048
1049 let s = ctx.textify_no_errors(&na);
1050 assert_eq!(s, "duplicated#231");
1051 }
1052}