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 #[allow(deprecated)]
408 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
409 ptype::Kind::IntervalYear(i) => {
410 textify_kind!(ctx, w, i, "interval_year")
411 }
412 #[allow(deprecated)]
413 ptype::Kind::TimestampTz(ts) => {
414 textify_kind!(ctx, w, ts, "timestamp_tz")
415 }
416 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
417
418 ptype::Kind::IntervalDay(i) => textify_type(
419 ctx,
420 w,
421 "interval_day",
422 i.nullability(),
423 i.type_variation_reference,
424 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
426 ),
427 ptype::Kind::IntervalCompound(i) => textify_type(
428 ctx,
429 w,
430 "interval_compound",
431 i.nullability(),
432 i.type_variation_reference,
433 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
434 ),
435 ptype::Kind::FixedChar(c) => textify_type(
436 ctx,
437 w,
438 "fixedchar",
439 c.nullability(),
440 c.type_variation_reference,
441 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
442 ),
443 ptype::Kind::Varchar(c) => textify_type(
444 ctx,
445 w,
446 "varchar",
447 c.nullability(),
448 c.type_variation_reference,
449 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
450 ),
451 ptype::Kind::FixedBinary(b) => textify_type(
452 ctx,
453 w,
454 "fixedbinary",
455 b.nullability(),
456 b.type_variation_reference,
457 Parameters(&[Some(Parameter::Integer(b.length as i64))]),
458 ),
459 ptype::Kind::Decimal(d) => {
460 let p = Parameter::Integer(d.precision as i64);
461 let s = Parameter::Integer(d.scale as i64);
462 textify_type(
463 ctx,
464 w,
465 "decimal",
466 d.nullability(),
467 d.type_variation_reference,
468 Parameters(&[Some(p), Some(s)]),
469 )
470 }
471 ptype::Kind::PrecisionTime(p) => textify_type(
472 ctx,
473 w,
474 "precisiontime",
475 p.nullability(),
476 p.type_variation_reference,
477 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
478 ),
479 ptype::Kind::PrecisionTimestamp(p) => textify_type(
480 ctx,
481 w,
482 "precisiontimestamp",
483 p.nullability(),
484 p.type_variation_reference,
485 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
486 ),
487 ptype::Kind::PrecisionTimestampTz(p) => textify_type(
488 ctx,
489 w,
490 "precisiontimestamptz",
491 p.nullability(),
492 p.type_variation_reference,
493 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
494 ),
495 ptype::Kind::Struct(s) => textify_type(
496 ctx,
497 w,
498 "struct",
499 s.nullability(),
500 s.type_variation_reference,
501 Parameters(
502 &s.types
503 .iter()
504 .map(|t| Some(Parameter::DataType(t.clone())))
505 .collect::<Vec<_>>(),
506 ),
507 ),
508 ptype::Kind::List(l) => {
509 let p = l
510 .r#type
511 .as_ref()
512 .map(|t| Parameter::DataType((**t).to_owned()));
513 textify_type(
514 ctx,
515 w,
516 "list",
517 l.nullability(),
518 l.type_variation_reference,
519 Parameters(&[p]),
520 )
521 }
522 ptype::Kind::Map(m) => {
523 let k = m
524 .key
525 .as_ref()
526 .map(|t| Parameter::DataType((**t).to_owned()));
527 let v = m
528 .value
529 .as_ref()
530 .map(|t| Parameter::DataType((**t).to_owned()));
531 textify_type(
532 ctx,
533 w,
534 "map",
535 m.nullability(),
536 m.type_variation_reference,
537 Parameters(&[k, v]),
538 )
539 }
540 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
541 #[allow(deprecated)]
542 ptype::Kind::UserDefinedTypeReference(r) => {
543 let udf = ptype::UserDefined {
546 type_reference: *r,
547 type_variation_reference: 0,
548 nullability: ptype::Nullability::Required as i32,
549 type_parameters: vec![],
550 };
551 ptype::Kind::UserDefined(udf).textify(ctx, w)
552 }
553 ptype::Kind::Func(_f) => {
554 write!(
555 w,
556 "{}",
557 ctx.failure(PlanError::unimplemented(
558 "FuncType",
559 Some("Func"),
560 "Function type textification not implemented",
561 ))
562 )
563 }
564 ptype::Kind::Alias(_p) => {
565 write!(
566 w,
567 "{}",
568 ctx.failure(PlanError::unimplemented(
569 "AliasType",
570 Some("Alias"),
571 "TypeAliasReference textification not implemented",
572 ))
573 )
574 }
575 }
576 }
577}
578
579impl Textify for proto::Type {
580 fn name() -> &'static str {
581 "Type"
582 }
583
584 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
585 write!(w, "{}", ctx.expect(self.kind.as_ref()))
586 }
587}
588
589#[cfg(test)]
685mod tests {
686
687 use super::*;
688 use crate::extensions::simple::{ExtensionKind, MissingReference};
689 use crate::fixtures::TestContext;
690 use crate::textify::foundation::FormatError;
691
692 #[test]
693 fn type_display() {
694 let ctx = TestContext::new()
695 .with_urn(1, "first")
696 .with_type_variation(1, 2, "u8");
697
698 let t = proto::Type {
699 kind: Some(ptype::Kind::Bool(ptype::Boolean {
700 type_variation_reference: 2,
701 nullability: ptype::Nullability::Nullable as i32,
702 })),
703 };
704
705 let s = ctx.textify_no_errors(&t);
706 assert_eq!(s, "boolean?[u8]");
707
708 let t = proto::Type {
709 kind: Some(ptype::Kind::I8(ptype::I8 {
710 type_variation_reference: 0,
711 nullability: ptype::Nullability::Required as i32,
712 })),
713 };
714 assert_eq!(ctx.textify_no_errors(&t), "i8");
715
716 let t = proto::Type {
717 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
718 type_variation_reference: 0,
719 nullability: ptype::Nullability::Nullable as i32,
720 precision: 3,
721 })),
722 };
723 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
724
725 let mut ctx = ctx.with_type_variation(1, 8, "int");
726 ctx.options.show_simple_extension_anchors = Visibility::Always;
727
728 let t = proto::Type {
729 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
730 type_variation_reference: 8,
731 nullability: ptype::Nullability::Nullable as i32,
732 precision: 9,
733 })),
734 };
735 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
736 }
737
738 #[test]
739 fn type_display_with_errors() {
740 let ctx = TestContext::new()
741 .with_urn(1, "first")
742 .with_type(1, 100, "cow");
743
744 let t = proto::Type {
745 kind: Some(ptype::Kind::Bool(ptype::Boolean {
746 type_variation_reference: 200,
747 nullability: ptype::Nullability::Nullable as i32,
748 })),
749 };
750 let (s, errs) = ctx.textify(&t);
751 assert_eq!(s, "boolean?[!{type_variation}#200]");
752 let err = errs.first();
753 let (&k, &a) = match err {
754 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
755 _ => panic!("Expected Lookup MissingAnchor: {err}"),
756 };
757
758 assert_eq!(k, ExtensionKind::TypeVariation);
759 assert_eq!(a, 200);
760
761 let t = proto::Type {
762 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
763 type_variation_reference: 0,
764 nullability: ptype::Nullability::Required as i32,
765 type_reference: 100,
766 type_parameters: vec![],
767 })),
768 };
769
770 let (s, errs) = ctx.textify(&t);
771 assert!(errs.is_empty());
772 assert_eq!(s, "cow");
773
774 let t = proto::Type {
775 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
776 type_variation_reference: 0,
777 nullability: ptype::Nullability::Required as i32,
778 type_reference: 12589,
779 type_parameters: vec![],
780 })),
781 };
782
783 let (s, errs) = ctx.textify(&t);
784 let err = errs.first();
785 let (&k, &a) = match err {
786 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
787 _ => panic!("Expected Lookup MissingAnchor: {err}"),
788 };
789 assert_eq!(k, ExtensionKind::Type);
790 assert_eq!(a, 12589);
791 assert_eq!(s, "!{type}#12589");
792 }
793
794 #[test]
795 fn struct_display() {
796 let ctx = TestContext::new();
797 let t = proto::Type {
798 kind: Some(ptype::Kind::Struct(ptype::Struct {
799 type_variation_reference: 0,
800 nullability: ptype::Nullability::Nullable as i32,
801 types: vec![
802 proto::Type {
803 kind: Some(ptype::Kind::String(ptype::String {
804 type_variation_reference: 0,
805 nullability: ptype::Nullability::Required as i32,
806 })),
807 },
808 proto::Type {
809 kind: Some(ptype::Kind::I8(ptype::I8 {
810 type_variation_reference: 0,
811 nullability: ptype::Nullability::Required as i32,
812 })),
813 },
814 proto::Type {
815 kind: Some(ptype::Kind::I32(ptype::I32 {
816 type_variation_reference: 0,
817 nullability: ptype::Nullability::Nullable as i32,
818 })),
819 },
820 proto::Type {
821 #[allow(deprecated)] kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
823 type_variation_reference: 0,
824 nullability: ptype::Nullability::Required as i32,
825 })),
826 },
827 ],
828 })),
829 };
830 assert_eq!(
831 ctx.textify_no_errors(&t),
832 "struct?<string, i8, i32?, timestamp_tz>"
833 );
834 }
835
836 #[test]
837 fn names_display() {
838 let ctx = TestContext::new();
839
840 let n = Name("name");
841 assert_eq!(ctx.textify_no_errors(&n), "name");
842
843 let n = Name("name with spaces");
844 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
845 }
846
847 fn overloaded_ctx() -> TestContext {
944 TestContext::new()
945 .with_urn(1, "substrait:functions_comparison")
946 .with_function(1, 1, "equal:any_any")
947 .with_function(1, 2, "equal:str_str")
948 .with_function(1, 3, "add:i64_i64")
949 }
950
951 #[test]
952 fn named_anchor_compact_unique_base_name_no_signature() {
953 let ctx = overloaded_ctx();
956 let eq = crate::textify::ErrorQueue::default();
957 let scope = ctx.scope(&eq);
958 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
959 assert!(na.base_name_unique, "add should have unique base name");
960 assert!(na.unique, "add:i64_i64 compound name should be unique");
961
962 let s = ctx.textify_no_errors(&na);
963 assert_eq!(s, "add");
964 }
965
966 #[test]
967 fn named_anchor_compact_overloaded_shows_signature() {
968 let ctx = overloaded_ctx();
972 let eq = crate::textify::ErrorQueue::default();
973 let scope = ctx.scope(&eq);
974 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
975 assert!(!na.base_name_unique, "equal base name should not be unique");
976 assert!(
977 na.unique,
978 "equal:any_any compound name should be unique across URNs"
979 );
980
981 let s = ctx.textify_no_errors(&na);
982 assert_eq!(s, "equal:any_any");
983 }
984
985 #[test]
986 fn named_anchor_verbose_unique_base_name_shows_signature_and_anchor() {
987 let mut ctx = overloaded_ctx();
989 ctx.options.show_simple_extension_anchors = Visibility::Always;
990
991 let eq = crate::textify::ErrorQueue::default();
992 let scope = ctx.scope(&eq);
993 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 3);
994 let s = ctx.textify_no_errors(&na);
995 assert_eq!(s, "add:i64_i64#3");
996 }
997
998 #[test]
999 fn named_anchor_verbose_overloaded_shows_signature_and_anchor() {
1000 let mut ctx = overloaded_ctx();
1002 ctx.options.show_simple_extension_anchors = Visibility::Always;
1003
1004 let eq = crate::textify::ErrorQueue::default();
1005 let scope = ctx.scope(&eq);
1006 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 2);
1007 let s = ctx.textify_no_errors(&na);
1008 assert_eq!(s, "equal:str_str#2");
1009 }
1010
1011 #[test]
1012 fn named_anchor_compact_same_compound_name_two_urns_shows_anchor() {
1013 let ctx = TestContext::new()
1016 .with_urn(1, "urn_a")
1017 .with_urn(2, "urn_b")
1018 .with_function(1, 1, "equal:any_any")
1019 .with_function(2, 2, "equal:any_any");
1020
1021 let eq = crate::textify::ErrorQueue::default();
1022 let scope = ctx.scope(&eq);
1023 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 1);
1024 assert!(!na.base_name_unique);
1025 assert!(!na.unique, "compound name not unique across two URNs");
1026
1027 let s = ctx.textify_no_errors(&na);
1028 assert_eq!(s, "equal:any_any#1");
1029 }
1030
1031 #[test]
1032 fn named_anchor_compact_plain_name_unique_no_signature_no_anchor() {
1033 let ctx = TestContext::new()
1034 .with_urn(1, "urn")
1035 .with_function(1, 10, "coalesce");
1036
1037 let eq = crate::textify::ErrorQueue::default();
1038 let scope = ctx.scope(&eq);
1039 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 10);
1040 assert!(na.base_name_unique);
1041 assert!(na.unique);
1042
1043 let s = ctx.textify_no_errors(&na);
1044 assert_eq!(s, "coalesce");
1045 }
1046
1047 #[test]
1048 fn named_anchor_compact_plain_name_non_unique_shows_anchor() {
1049 let ctx = TestContext::new()
1050 .with_urn(1, "urn1")
1051 .with_urn(2, "urn2")
1052 .with_function(1, 231, "duplicated")
1053 .with_function(2, 232, "duplicated");
1054
1055 let eq = crate::textify::ErrorQueue::default();
1056 let scope = ctx.scope(&eq);
1057 let na = NamedAnchor::lookup(&scope, ExtensionKind::Function, 231);
1058 assert!(!na.base_name_unique);
1059 assert!(!na.unique);
1060
1061 let s = ctx.textify_no_errors(&na);
1062 assert_eq!(s, "duplicated#231");
1063 }
1064}