1use crate::v1::functor;
55use crate::v1::surface::{self, ModSurface, SurfaceEnv};
56use rustyfi_syntax::cst;
57use rustyfi_syntax::cst_v1::{self, ast as ast_v1};
58use rustyfi_syntax::leaf::*;
59use rustyfi_syntax::Span;
60
61#[derive(Debug, Clone, thiserror::Error)]
64#[error(
65 "{span}: SATySFi 0.1 construct not supported yet in this port's Slice 1: {construct} ({hint})"
66)]
67pub struct LowerError {
68 pub construct: &'static str,
69 pub hint: &'static str,
70 pub span: Span,
71}
72
73fn unsupported(construct: &'static str, hint: &'static str, span: Span) -> LowerError {
74 LowerError {
75 construct,
76 hint,
77 span,
78 }
79}
80
81struct AbsolutizeRewrite<'a, 's> {
98 surfaces: &'a SurfaceEnv<'s>,
99}
100
101impl functor::HeadRewrite for AbsolutizeRewrite<'_, '_> {
102 fn rewrite(
103 &self,
104 mods: &[String],
105 path: &[String],
106 _span: Span,
107 ) -> Result<Option<Vec<String>>, LowerError> {
108 let Some(head) = mods.first() else {
109 return Ok(None);
110 };
111 let Some((resolved, _)) = surface::resolve_module(self.surfaces, path, head) else {
112 return Ok(None);
113 };
114 if resolved == *head {
115 return Ok(None);
118 }
119 let mut out: Vec<String> = resolved.split('.').map(str::to_string).collect();
120 out.extend(mods[1..].iter().cloned());
121 Ok(Some(out))
122 }
123
124 fn rewrite_bare_names(&self) -> bool {
131 false
132 }
133
134 fn walk_signatures(&self) -> bool {
135 false
136 }
137
138 fn reject_nested_functor_literals(&self) -> bool {
144 false
145 }
146}
147
148#[derive(Clone, Default)]
169pub(crate) struct TypeNameEnv(std::collections::HashMap<String, String>);
170
171pub(crate) fn qualify_type_key(mod_path: &[String], local: &str) -> String {
178 if mod_path.is_empty() {
179 local.to_string()
180 } else {
181 format!("{}.{}", mod_path.join("."), local)
182 }
183}
184
185impl TypeNameEnv {
186 pub(crate) fn qualify(&self, bare: &str) -> String {
187 self.0
188 .get(bare)
189 .cloned()
190 .unwrap_or_else(|| bare.to_string())
191 }
192
193 pub(crate) fn child<'a, 's>(
214 &self,
215 mod_path: &[String],
216 binds: impl Iterator<Item = &'a cst_v1::Bind>,
217 surfaces: &SurfaceEnv<'s>,
218 ) -> Self {
219 let mut map = self.0.clone();
220 for b in binds {
221 match b {
222 cst_v1::Bind::Type { first, ands, .. } => {
223 map.insert(
224 first.name.name.clone(),
225 qualify_type_key(mod_path, &first.name.name),
226 );
227 for a in ands {
228 map.insert(
229 a.bind.name.name.clone(),
230 qualify_type_key(mod_path, &a.bind.name.name),
231 );
232 }
233 }
234 cst_v1::Bind::Include { kw, body } => match &*body.0 {
235 ast_v1::ModExpr::Var(_) => {
236 if let Some(Some(target)) =
237 surface::frozen_include_target(surfaces, mod_path, kw.0)
238 {
239 if let Some(target_surf) = surfaces.modules.get(target) {
240 for (t, _) in &target_surf.types {
241 map.insert(t.clone(), qualify_type_key(mod_path, t));
242 }
243 }
244 }
245 }
246 ast_v1::ModExpr::App { func, arg: _ } => {
255 let app_span = mod_chain_span(func);
256 if let Some(Some(surface::AppResolution { functor_path, .. })) =
257 surface::frozen_app_target(surfaces, mod_path, app_span)
258 {
259 if let Some(fdef) = surfaces.functors.get(functor_path) {
260 if let Some(body_binds) = functor::functor_body_binds(fdef.body) {
261 for b in body_binds.iter().map(|sb| sb.0.as_ref()) {
262 if let cst_v1::Bind::Type { first, ands, .. } = b {
263 map.insert(
264 first.name.name.clone(),
265 qualify_type_key(mod_path, &first.name.name),
266 );
267 for a in ands {
268 map.insert(
269 a.bind.name.name.clone(),
270 qualify_type_key(mod_path, &a.bind.name.name),
271 );
272 }
273 }
274 }
275 }
276 }
277 }
278 }
279 ast_v1::ModExpr::Struct { .. }
280 | ast_v1::ModExpr::Coerce { .. }
281 | ast_v1::ModExpr::Functor { .. } => {}
282 },
283 _ => {}
284 }
285 }
286 TypeNameEnv(map)
287 }
288
289 pub(crate) fn child_from_names(
304 &self,
305 mod_path: &[String],
306 names: impl Iterator<Item = String>,
307 ) -> Self {
308 let mut map = self.0.clone();
309 for n in names {
310 let q = qualify_type_key(mod_path, &n);
311 map.insert(n, q);
312 }
313 TypeNameEnv(map)
314 }
315}
316
317pub fn lower_file_v1(file: &cst_v1::FileV1) -> Result<Vec<cst::TopBinding>, LowerError> {
323 let mut surfaces = SurfaceEnv::default();
334 surface::build_file_surface(file, &mut surfaces);
335 lower_file_v1_with_surfaces(file, &surfaces)
336}
337
338pub(crate) fn lower_file_v1_with_surfaces<'a>(
348 file: &'a cst_v1::FileV1,
349 surfaces: &SurfaceEnv<'a>,
350) -> Result<Vec<cst::TopBinding>, LowerError> {
351 match file {
352 cst_v1::FileV1::Library {
353 module_kw,
354 name,
355 sig_annot: _,
356 eq,
357 struct_kw,
358 binds,
359 end_kw,
360 ..
361 } => {
362 let module = lower_module_bind(
366 module_kw,
367 name,
368 eq,
369 struct_kw,
370 binds,
371 end_kw,
372 &[],
373 &TypeNameEnv::default(),
374 surfaces,
375 )?;
376 Ok(vec![module])
377 }
378 cst_v1::FileV1::Document { eoi, .. } => Err(unsupported(
379 "a document file used as a dependency library",
380 "the loader's DocumentAsDependency check should have rejected \
381 this before lowering ever ran",
382 eoi.0,
383 )),
384 }
385}
386
387fn lower_module_bind<'a, 's>(
399 module_kw: &KwModule,
400 name: &CtorTok,
401 eq: &DefEqTok,
402 struct_kw: &KwStruct,
403 binds: impl IntoIterator<Item = &'a cst_v1::Bind>,
404 end_kw: &KwEnd,
405 mod_path: &[String],
406 tyenv: &TypeNameEnv,
407 surfaces: &SurfaceEnv<'s>,
408) -> Result<cst::TopBinding, LowerError> {
409 let mut child_path = mod_path.to_vec();
410 child_path.push(name.name.clone());
411 let absolutized = functor::rewrite_binds(binds, &AbsolutizeRewrite { surfaces }, &child_path)?;
420 let child_tyenv = tyenv.child(&child_path, absolutized.iter(), surfaces);
421 let mut decls = Vec::new();
422 for b in &absolutized {
423 for tb in lower_bind_v1(b, &child_path, &child_tyenv, surfaces)? {
424 decls.push(cst::StructDecl(Box::new(tb)));
425 }
426 }
427 Ok(cst::TopBinding::Module {
428 kw: module_kw.clone(),
429 name: name.clone(),
430 sig: None,
431 eq: eq.clone(),
432 struct_kw: struct_kw.clone(),
433 decls,
434 end_kw: end_kw.clone(),
435 })
436}
437
438pub fn lower_document_v1(file: &cst_v1::FileV1) -> Result<cst::ast::Expr, LowerError> {
442 match file {
443 cst_v1::FileV1::Document { body, .. } => lower_expr(body),
444 cst_v1::FileV1::Library { end_kw, .. } => Err(unsupported(
445 "a library file used as the entry document",
446 "the loader's LibraryAsEntry check should have rejected this \
447 before lowering ever ran",
448 end_kw.0,
449 )),
450 }
451}
452
453fn lower_bind_v1<'s>(
462 b: &cst_v1::Bind,
463 mod_path: &[String],
464 tyenv: &TypeNameEnv,
465 surfaces: &SurfaceEnv<'s>,
466) -> Result<Vec<cst::TopBinding>, LowerError> {
467 match b {
468 cst_v1::Bind::Value {
469 kw,
470 stage,
471 name,
472 params,
473 eq,
474 body,
475 } => {
476 let (ps, value) = lower_param_units(params, lower_expr(body)?)?;
477 Ok(vec![cst::TopBinding::Let(cst::TopLet {
478 let_kw: KwLet(kw.0),
479 stage: stage.as_ref().map(lower_bind_stage),
485 name: name.clone(),
486 ascription: None,
487 leading_bar: None,
489 params: ps,
490 eq: eq.clone(),
491 value,
492 })])
493 }
494 cst_v1::Bind::ValueInline {
495 kw,
496 stage,
497 ctx,
498 cmd,
499 params,
500 eq,
501 body,
502 ..
503 } => Ok(vec![cst::TopBinding::LetInline {
504 kw: KwLetHorz(kw.0),
505 stage: stage.as_ref().map(lower_bind_stage),
506 ctx: ctx.clone(),
507 cmd: plain_horz(cmd)?,
508 params: lower_command_params(params)?,
509 eq: eq.clone(),
510 value: lower_expr(body)?,
511 }]),
512 cst_v1::Bind::ValueBlock {
513 kw,
514 stage,
515 ctx,
516 cmd,
517 params,
518 eq,
519 body,
520 ..
521 } => Ok(vec![cst::TopBinding::LetBlock {
522 kw: KwLetVert(kw.0),
523 stage: stage.as_ref().map(lower_bind_stage),
524 ctx: ctx.clone(),
525 cmd: plain_vert(cmd)?,
526 params: lower_command_params(params)?,
527 eq: eq.clone(),
528 value: lower_expr(body)?,
529 }]),
530 cst_v1::Bind::ValueMath {
531 kw,
532 stage,
533 ctx,
534 cmd,
535 params,
536 scripts,
537 eq,
538 body,
539 ..
540 } => Ok(vec![lower_value_math(
541 kw, stage, ctx, cmd, params, scripts, eq, body,
542 )?]),
543 cst_v1::Bind::ValueRec {
544 kw,
545 stage,
546 first,
547 ands,
548 ..
549 } => Ok(vec![cst::TopBinding::LetRec {
550 kw: KwLetRec(kw.0),
551 stage: stage.as_ref().map(lower_bind_stage),
552 first: lower_rec_clause(first)?,
553 ands: ands
554 .iter()
555 .map(|a| {
556 Ok(cst::ast::AndBinding {
557 and_kw: a.and_kw.clone(),
558 binding: lower_rec_clause(&a.clause)?,
559 })
560 })
561 .collect::<Result<_, LowerError>>()?,
562 }]),
563 cst_v1::Bind::ValueMutable {
564 kw,
565 stage,
566 name,
567 arrow,
568 value,
569 ..
570 } => Ok(vec![cst::TopBinding::LetMutable {
571 kw: KwLetMutable(kw.0),
572 stage: stage.as_ref().map(lower_bind_stage),
573 name: name.clone(),
574 arrow: arrow.clone(),
575 value: lower_expr(value)?,
576 }]),
577 cst_v1::Bind::Type { kw, first, ands } => {
578 let mut out = Vec::with_capacity(1 + ands.len());
579 out.push(lower_type_single(kw, first, tyenv)?);
580 for a in ands {
581 out.push(lower_type_single(kw, &a.bind, tyenv)?);
582 }
583 Ok(out)
584 }
585 cst_v1::Bind::Module {
586 module_kw,
587 name,
588 sig_annot: _,
589 eq,
590 body,
591 } => match &*body.0 {
592 ast_v1::ModExpr::Struct {
593 struct_kw,
594 binds,
595 end_kw,
596 } => {
597 let module = lower_module_bind(
601 module_kw,
602 name,
603 eq,
604 struct_kw,
605 binds.iter().map(|b| b.0.as_ref()),
606 end_kw,
607 mod_path,
608 tyenv,
609 surfaces,
610 )?;
611 Ok(vec![module])
612 }
613 ast_v1::ModExpr::Var(chain) => Ok(vec![lower_module_alias(
619 module_kw,
620 name,
621 eq,
622 mod_path,
623 surfaces,
624 &chain.render(),
625 mod_chain_span(chain),
626 )?]),
627 ast_v1::ModExpr::App { func, arg: _ } => {
640 let app_span = mod_chain_span(func);
641 match surface::frozen_app_target(surfaces, mod_path, app_span) {
642 Some(Some(surface::AppResolution {
643 functor_path,
644 arg_path,
645 })) => {
646 let fdef = surfaces
647 .functors
648 .get(functor_path)
649 .expect("a frozen app target always names a registered functor");
650 let body_binds = functor::functor_body_binds(fdef.body).expect(
651 "a frozen app target's functor body is always struct-shaped \
652 (a non-struct body never freezes a resolution)",
653 );
654 let arg_segs: Vec<String> =
655 arg_path.split('.').map(str::to_string).collect();
656 let substituted =
657 functor::substitute_binds(body_binds, &fdef.param, &arg_segs)?;
658 let span = name.span;
659 let module = lower_module_bind(
660 module_kw,
661 name,
662 eq,
663 &KwStruct(span),
664 substituted.iter().map(|sb| sb.0.as_ref()),
665 &KwEnd(span),
666 mod_path,
667 tyenv,
668 surfaces,
669 )?;
670 Ok(vec![module])
671 }
672 _ => Err(unsupported(
673 "a functor application whose functor or argument is unknown",
674 "an application must name an earlier, concrete module already \
675 in scope (or an enclosing functor's own parameter, once that \
676 functor is itself applied)",
677 mod_chain_span(func),
678 )),
679 }
680 }
681 ast_v1::ModExpr::Functor { .. } => Ok(Vec::new()),
687 ast_v1::ModExpr::Coerce { name: target, .. } => Ok(vec![lower_module_alias(
693 module_kw,
694 name,
695 eq,
696 mod_path,
697 surfaces,
698 &target.name,
699 target.span,
700 )?]),
701 },
702 cst_v1::Bind::Signature { .. } => Ok(Vec::new()),
706 cst_v1::Bind::Include { kw, body } => match &*body.0 {
718 ast_v1::ModExpr::Var(chain) => {
719 match surface::frozen_include_target(surfaces, mod_path, kw.0) {
720 Some(Some(target_path)) => {
721 let target_surf = surfaces
722 .modules
723 .get(target_path)
724 .expect("a frozen include target is always a registered module");
725 let decls = alias_member_decls(kw.0, mod_path, target_path, target_surf)?;
726 Ok(decls.into_iter().map(|sd| *sd.0).collect())
727 }
728 _ => Err(unsupported(
729 "an `include M` binding naming an unknown module",
730 "an include must name an earlier module already in scope",
731 mod_chain_span(chain),
732 )),
733 }
734 }
735 ast_v1::ModExpr::App { func, arg: _ } => {
745 let app_span = mod_chain_span(func);
746 match surface::frozen_app_target(surfaces, mod_path, app_span) {
747 Some(Some(surface::AppResolution {
748 functor_path,
749 arg_path,
750 })) => {
751 let fdef = surfaces
752 .functors
753 .get(functor_path)
754 .expect("a frozen app target always names a registered functor");
755 let body_binds = functor::functor_body_binds(fdef.body).expect(
756 "a frozen app target's functor body is always struct-shaped \
757 (a non-struct body never freezes a resolution)",
758 );
759 let arg_segs: Vec<String> =
760 arg_path.split('.').map(str::to_string).collect();
761 let substituted =
762 functor::substitute_binds(body_binds, &fdef.param, &arg_segs)?;
763 let mut out = Vec::new();
764 for b in substituted.iter().map(|sb| sb.0.as_ref()) {
765 out.extend(lower_bind_v1(b, mod_path, tyenv, surfaces)?);
766 }
767 Ok(out)
768 }
769 _ => Err(unsupported(
770 "an `include` of a functor application whose functor or argument \
771 is unknown",
772 "an include must name an earlier, concrete module already in \
773 scope (or an enclosing functor's own parameter, once that \
774 functor is itself applied)",
775 mod_chain_span(func),
776 )),
777 }
778 }
779 ast_v1::ModExpr::Functor { fun_kw, .. } => Err(unsupported(
783 "an `include` of a functor literal (`fun (X : S) -> ...`)",
784 "a functor is not a module value — apply it first \
785 (`include Make Arg`), or name the application \
786 (`module M = Make Arg include M`)",
787 fun_kw.0,
788 )),
789 ast_v1::ModExpr::Struct { struct_kw, .. } => Err(unsupported(
790 "an `include` of an inline `struct … end` literal",
791 "name the module first: `module N = struct … end include N`",
792 struct_kw.0,
793 )),
794 ast_v1::ModExpr::Coerce { name: target, .. } => Err(unsupported(
795 "an `include` of a coerced module",
796 "seal a named module first, then include it",
797 target.span,
798 )),
799 },
800 }
801}
802
803fn mod_chain_span(c: &ast_v1::ModChainV1) -> Span {
806 match c {
807 ast_v1::ModChainV1::Long(t) => t.span,
808 ast_v1::ModChainV1::Single(t) => t.span,
809 }
810}
811
812fn lower_module_alias(
825 module_kw: &KwModule,
826 name: &CtorTok,
827 eq: &DefEqTok,
828 mod_path: &[String],
829 surfaces: &SurfaceEnv,
830 _chain_rendered: &str,
831 chain_span: Span,
832) -> Result<cst::TopBinding, LowerError> {
833 let span = name.span;
834 let mut alias_path = mod_path.to_vec();
835 alias_path.push(name.name.clone());
836 let target_path = match surface::frozen_alias_target(surfaces, &alias_path) {
844 Some(Some(t)) => t.clone(),
845 _ => {
846 return Err(unsupported(
847 "a module alias/path binding (`module M = N`) naming an unknown module",
848 "a module alias must name an earlier module already in scope",
849 chain_span,
850 ));
851 }
852 };
853 let surface = surfaces
854 .modules
855 .get(&target_path)
856 .expect("a frozen alias target is always a registered module");
857 let decls = alias_member_decls(span, &alias_path, &target_path, surface)?;
858 Ok(cst::TopBinding::Module {
859 kw: module_kw.clone(),
860 name: name.clone(),
861 sig: None,
862 eq: eq.clone(),
863 struct_kw: KwStruct(span),
864 decls,
865 end_kw: KwEnd(span),
866 })
867}
868
869fn alias_member_decls(
878 span: Span,
879 alias_path: &[String],
880 target_path: &str,
881 surface: &ModSurface,
882) -> Result<Vec<cst::StructDecl>, LowerError> {
883 let mut out = Vec::with_capacity(surface.vals.len() + surface.types.len() + surface.mods.len());
884 for x in &surface.vals {
885 let target_ref = apply_chain(
886 cst::ast::Atomic::VarWithMod(VarWithModTok {
887 mods: vec![target_path.to_string()],
888 name: x.clone(),
889 span,
890 }),
891 Vec::new(),
892 );
893 out.push(cst::StructDecl(Box::new(cst::TopBinding::Let(
894 cst::TopLet {
895 let_kw: KwLet(span),
896 stage: None,
899 name: cst::BindName::from(var_tok(x, span)),
900 ascription: None,
901 leading_bar: None,
903 params: Vec::new(),
904 eq: DefEqTok(span),
905 value: target_ref,
906 },
907 ))));
908 }
909 for (tname, arity) in &surface.types {
910 let ctor = var_tok(&format!("{target_path}.{tname}"), span);
911 let (tyvars, ty) = match *arity {
912 0 => (
913 Vec::new(),
914 cst::ast::TypeExpr::Atom(cst::ast::TypeProd {
915 first: cst::ast::TypeApp {
916 head: cst::ast::TypeAtom::Name(ctor),
917 rest: Vec::new(),
918 },
919 rest: Vec::new(),
920 }),
921 ),
922 1 => {
923 let tv = TypeVarTok {
924 name: "a".to_string(),
925 span,
926 };
927 (
928 vec![tv.clone()],
929 cst::ast::TypeExpr::Atom(cst::ast::TypeProd {
930 first: cst::ast::TypeApp {
931 head: cst::ast::TypeAtom::Var(tv),
932 rest: vec![cst::ast::TypeAtom::Name(ctor)],
933 },
934 rest: Vec::new(),
935 }),
936 )
937 }
938 _ => {
939 return Err(unsupported(
940 "an alias copy of a type member with arity >= 2",
941 "the 0.0.6 cst target (`TypeApp`) is single-argument \
942 (cst.rs:1297-1312) — widen it only when a real \
943 package needs arity >= 2",
944 span,
945 ));
946 }
947 };
948 out.push(cst::StructDecl(Box::new(cst::TopBinding::Type(
949 cst::TypeDecl {
950 kw: KwType(span),
951 tyvars,
952 name: var_tok(&qualify_type_key(alias_path, tname), span),
953 eq: DefEqTok(span),
954 body: cst::TypeDeclBody::Synonym(ty),
955 ands: Vec::new(),
956 },
957 ))));
958 }
959 for (qname, child) in &surface.mods {
960 let mut child_alias_path = alias_path.to_vec();
961 child_alias_path.push(qname.clone());
962 let child_target = format!("{target_path}.{qname}");
963 let child_decls = alias_member_decls(span, &child_alias_path, &child_target, child)?;
964 out.push(cst::StructDecl(Box::new(cst::TopBinding::Module {
965 kw: KwModule(span),
966 name: CtorTok {
967 name: qname.clone(),
968 span,
969 },
970 sig: None,
971 eq: DefEqTok(span),
972 struct_kw: KwStruct(span),
973 decls: child_decls,
974 end_kw: KwEnd(span),
975 })));
976 }
977 Ok(out)
981}
982
983fn lower_rec_clause(c: &ast_v1::RecClauseV1) -> Result<cst::ast::RecBinding, LowerError> {
987 let value_expr = lower_expr(&c.value.0)?;
988 let (params, value) = if c.params.iter().all(|p| p.opts.is_none()) {
994 let ps = c
995 .params
996 .iter()
997 .map(|p| lower_param_body(&p.body))
998 .collect::<Result<_, _>>()?;
999 (ps, value_expr)
1000 } else {
1001 let (_, chain) = lower_param_units(&c.params, value_expr)?;
1002 (Vec::new(), chain)
1003 };
1004 Ok(cst::ast::RecBinding {
1005 name: c.name.clone(),
1008 ascription: None,
1010 leading_bar: None,
1012 params,
1013 eq: c.eq.clone(),
1014 value: erase_expr(value),
1015 extra: Vec::new(),
1017 })
1018}
1019
1020fn lower_type_single(
1023 kw: &KwType,
1024 s: &cst_v1::TypeBindSingleV1,
1025 tyenv: &TypeNameEnv,
1026) -> Result<cst::TopBinding, LowerError> {
1027 Ok(cst::TopBinding::Type(cst::TypeDecl {
1028 kw: kw.clone(),
1030 tyvars: s.tyvars.clone(),
1032 name: VarTok {
1033 name: tyenv.qualify(&s.name.name),
1034 span: s.name.span,
1035 },
1036 eq: s.eq.clone(),
1037 body: match &s.body {
1038 cst_v1::TypeBodyV1::Variant {
1039 leading_bar,
1040 first,
1041 rest,
1042 } => cst::TypeDeclBody::Variant {
1043 leading_bar: leading_bar.clone(),
1044 first: lower_variant_def(first, tyenv)?,
1045 rest: rest
1046 .iter()
1047 .map(|b| {
1048 Ok(cst::BarVariantDef {
1049 bar: b.bar.clone(),
1050 def: lower_variant_def(&b.def, tyenv)?,
1051 })
1052 })
1053 .collect::<Result<_, LowerError>>()?,
1054 },
1055 cst_v1::TypeBodyV1::Synonym(ty) => {
1056 cst::TypeDeclBody::Synonym(lower_type_expr(ty, tyenv)?)
1057 }
1058 },
1059 ands: Vec::new(),
1062 }))
1063}
1064
1065fn lower_variant_def(
1066 v: &cst_v1::VariantDefV1,
1067 tyenv: &TypeNameEnv,
1068) -> Result<cst::VariantDef, LowerError> {
1069 Ok(cst::VariantDef {
1070 ctor: v.ctor.clone(),
1072 of_ty: v
1073 .of_ty
1074 .as_ref()
1075 .map(|o| {
1076 Ok(cst::OfType {
1077 of_kw: o.of_kw.clone(),
1078 ty: lower_type_expr(&o.ty, tyenv)?,
1079 })
1080 })
1081 .transpose()?,
1082 })
1083}
1084
1085pub(crate) fn lower_type_expr(
1086 t: &ast_v1::TypeExpr,
1087 tyenv: &TypeNameEnv,
1088) -> Result<cst::ast::TypeExpr, LowerError> {
1089 Ok(match t {
1090 ast_v1::TypeExpr::Fun { dom, arrow, cod } => cst::ast::TypeExpr::Fun {
1091 opts: Vec::new(),
1096 dom: lower_type_prod(dom, tyenv)?,
1097 arrow: arrow.clone(),
1098 cod: Box::new(lower_type_expr(cod, tyenv)?),
1099 },
1100 ast_v1::TypeExpr::Atom(p) => cst::ast::TypeExpr::Atom(lower_type_prod(p, tyenv)?),
1101 ast_v1::TypeExpr::OptRowFun {
1109 opt_dom,
1110 dom,
1111 arrow,
1112 cod,
1113 } => {
1114 if let Some(tail) = &opt_dom.inner.row_tail {
1115 return Err(unsupported(
1116 "a row-variable tail in an optional-argument type domain (`| ?'r`)",
1117 "row quantification arrives with signature enforcement — \
1118 roadmap L4 / Sub-slice 2d",
1119 tail.var.span,
1120 ));
1121 }
1122 if opt_dom.inner.entries.is_empty() {
1123 return Err(unsupported(
1124 "an empty `?()` optional-argument type domain",
1125 "a `?(…)` domain must bind at least one label",
1126 opt_dom.q.0,
1127 ));
1128 }
1129 cst::ast::TypeExpr::OptRowFun {
1130 opt_dom: cst::ast::CstTypeOptDom {
1131 q: opt_dom.q.clone(),
1132 paren: clone_paren(&opt_dom.paren),
1133 entries: opt_dom
1134 .inner
1135 .entries
1136 .iter()
1137 .map(|e| {
1138 Ok(cst::ast::CstTypeOptEntry {
1139 label: e.label.clone(), colon: e.colon.clone(),
1141 ty: cst::TyErased(Box::new(lower_type_expr(&e.ty.0, tyenv)?)),
1142 comma: e.comma.clone(),
1143 })
1144 })
1145 .collect::<Result<_, LowerError>>()?,
1146 },
1147 dom: lower_type_prod(dom, tyenv)?,
1148 arrow: arrow.clone(),
1149 cod: Box::new(lower_type_expr(cod, tyenv)?),
1150 }
1151 }
1152 })
1153}
1154
1155fn lower_type_prod(
1156 p: &ast_v1::TypeProd,
1157 tyenv: &TypeNameEnv,
1158) -> Result<cst::ast::TypeProd, LowerError> {
1159 Ok(cst::ast::TypeProd {
1160 first: lower_type_app(&p.first, tyenv)?,
1161 rest: p
1162 .rest
1163 .iter()
1164 .map(|s| {
1165 Ok(cst::ast::StarType {
1166 star: s.star.clone(),
1167 ty: lower_type_app(&s.ty, tyenv)?,
1168 })
1169 })
1170 .collect::<Result<_, LowerError>>()?,
1171 })
1172}
1173
1174fn lower_type_app(
1183 a: &ast_v1::TypeApp,
1184 tyenv: &TypeNameEnv,
1185) -> Result<cst::ast::TypeApp, LowerError> {
1186 match a {
1187 ast_v1::TypeApp::InlineCmdTy { kw, ilist: list, args } => Ok(cst::ast::TypeApp {
1188 head: cst::ast::TypeAtom::Cmd {
1189 list: list.clone(),
1190 args: lower_type_cmd_args(args, tyenv)?,
1191 kind: cst::ast::CmdTypeKind::Inline(HorzCmdTypeTok(kw.0)),
1192 },
1193 rest: Vec::new(),
1194 }),
1195 ast_v1::TypeApp::BlockCmdTy { kw, blist: list, args } => Ok(cst::ast::TypeApp {
1196 head: cst::ast::TypeAtom::Cmd {
1197 list: list.clone(),
1198 args: lower_type_cmd_args(args, tyenv)?,
1199 kind: cst::ast::CmdTypeKind::Block(VertCmdTypeTok(kw.0)),
1200 },
1201 rest: Vec::new(),
1202 }),
1203 ast_v1::TypeApp::MathCmdTy { kw, mlist: list, args } => Ok(cst::ast::TypeApp {
1208 head: cst::ast::TypeAtom::Cmd {
1209 list: list.clone(),
1210 args: lower_type_cmd_args(args, tyenv)?,
1211 kind: cst::ast::CmdTypeKind::Math(MathCmdTypeTok(kw.0)),
1212 },
1213 rest: Vec::new(),
1214 }),
1215 ast_v1::TypeApp::AppliedLong { ctor, first, rest } => lower_applied(
1220 first,
1221 rest,
1222 qualify_type_key(&ctor.mods, &ctor.name),
1223 ctor.span,
1224 tyenv,
1225 ),
1226 ast_v1::TypeApp::Applied { ctor, first, rest } => {
1227 let name = tyenv.qualify(&ctor.name);
1228 lower_applied(first, rest, name, ctor.span, tyenv)
1229 }
1230 ast_v1::TypeApp::Atom(at) => Ok(cst::ast::TypeApp {
1231 head: lower_type_atom(at, tyenv)?,
1232 rest: Vec::new(),
1233 }),
1234 }
1235}
1236
1237fn lower_applied(
1241 first: &ast_v1::TypeAtom,
1242 rest: &[ast_v1::TypeAtom],
1243 ctor_name: String,
1244 ctor_span: Span,
1245 tyenv: &TypeNameEnv,
1246) -> Result<cst::ast::TypeApp, LowerError> {
1247 let head = lower_type_atom(first, tyenv)?;
1248 let mut out_rest: Vec<cst::ast::TypeAtom> = Vec::with_capacity(rest.len() + 1);
1249 for a in rest {
1250 out_rest.push(lower_type_atom(a, tyenv)?);
1251 }
1252 out_rest.push(cst::ast::TypeAtom::Name(VarTok {
1253 name: ctor_name,
1254 span: ctor_span,
1255 }));
1256 Ok(cst::ast::TypeApp {
1257 head,
1258 rest: out_rest,
1259 })
1260}
1261
1262fn lower_type_cmd_args(
1274 args: &[ast_v1::TypeCmdArgItemV1],
1275 tyenv: &TypeNameEnv,
1276) -> Result<Vec<cst::ast::TypeCmdArgItem>, LowerError> {
1277 args.iter()
1278 .map(|a| {
1279 Ok(cst::ast::TypeCmdArgItem {
1280 opt_labels: match &a.opts {
1281 None => Vec::new(),
1282 Some(dom) => {
1283 if dom.entries.is_empty() {
1284 return Err(unsupported(
1285 "an empty `?()` command-type optional-label bundle",
1286 "a `?(…)` bundle must bind at least one label",
1287 dom.q.0,
1288 ));
1289 }
1290 dom.entries
1291 .iter()
1292 .map(|e| {
1293 Ok(cst::ast::TypeCmdOptField {
1294 label: e.label.clone(),
1295 colon: e.colon.clone(),
1296 ty: cst::TyErased(Box::new(lower_type_expr(&e.ty.0, tyenv)?)),
1297 comma: e.comma.clone(),
1298 })
1299 })
1300 .collect::<Result<_, LowerError>>()?
1301 }
1302 },
1303 ty: cst::TyErased(Box::new(lower_type_expr(&a.ty.0, tyenv)?)),
1304 opt: None,
1305 semi: None,
1306 })
1307 })
1308 .collect()
1309}
1310
1311fn lower_type_atom(
1312 a: &ast_v1::TypeAtom,
1313 tyenv: &TypeNameEnv,
1314) -> Result<cst::ast::TypeAtom, LowerError> {
1315 Ok(match a {
1316 ast_v1::TypeAtom::Paren { paren, inner } => cst::ast::TypeAtom::Paren {
1317 paren: paren.clone(),
1318 inner: cst::TyErased(Box::new(lower_type_expr(&inner.0, tyenv)?)),
1319 },
1320 ast_v1::TypeAtom::Record { rec, inner } if inner.row_tail.is_none() => {
1328 cst::ast::TypeAtom::Record {
1329 rec: rec.clone(),
1330 fields: inner
1331 .fields
1332 .iter()
1333 .map(|f| {
1334 Ok(cst::ast::TypeRecordField {
1335 name: f.name.clone(), colon: f.colon.clone(), ty: cst::TyErased(Box::new(lower_type_expr(&f.ty.0, tyenv)?)),
1338 semi: None,
1342 })
1343 })
1344 .collect::<Result<_, LowerError>>()?,
1345 }
1346 }
1347 ast_v1::TypeAtom::Record { rec, inner } => {
1348 let tail = inner.row_tail.as_ref().expect("guarded by the arm above");
1349 cst::ast::TypeAtom::RecordOpen {
1350 orec: rec.clone(),
1351 inner: cst::ast::CstRecordOpenInner {
1352 fields: inner
1353 .fields
1354 .iter()
1355 .map(|f| {
1356 Ok(cst::ast::CstRecordOpenField {
1357 name: f.name.clone(),
1358 colon: f.colon.clone(),
1359 ty: cst::TyErased(Box::new(lower_type_expr(&f.ty.0, tyenv)?)),
1360 comma: None,
1361 })
1362 })
1363 .collect::<Result<_, LowerError>>()?,
1364 bar: tail.bar.clone(),
1365 var: tail.var.clone(),
1366 },
1367 }
1368 }
1369 ast_v1::TypeAtom::Var(v) => cst::ast::TypeAtom::Var(v.clone()),
1370 ast_v1::TypeAtom::LongName(t) => cst::ast::TypeAtom::Name(VarTok {
1373 name: qualify_type_key(&t.mods, &t.name),
1374 span: t.span,
1375 }),
1376 ast_v1::TypeAtom::Name(n) => cst::ast::TypeAtom::Name(VarTok {
1377 name: tyenv.qualify(&n.name),
1378 span: n.span,
1379 }),
1380 })
1381}
1382
1383
1384fn plain_horz(name: &AnyHorzCmdTok) -> Result<HorzCmdTok, LowerError> {
1385 match name {
1386 AnyHorzCmdTok::Plain(t) => Ok(t.clone()),
1387 AnyHorzCmdTok::Mod(t) => Err(unsupported(
1388 "a module-qualified command name in binding position",
1389 "the cst target field (`LetInline::cmd`) is a bare `HorzCmdTok` \
1390 — not valid 0.1 syntax",
1391 t.span,
1392 )),
1393 }
1394}
1395
1396fn plain_vert(name: &AnyVertCmdTok) -> Result<VertCmdTok, LowerError> {
1397 match name {
1398 AnyVertCmdTok::Plain(t) => Ok(t.clone()),
1399 AnyVertCmdTok::Mod(t) => Err(unsupported(
1400 "a module-qualified command name in binding position",
1401 "the cst target field (`LetBlock::cmd`) is a bare `VertCmdTok` \
1402 — not valid 0.1 syntax",
1403 t.span,
1404 )),
1405 }
1406}
1407
1408fn lower_param_units(
1420 params: &[cst_v1::Param],
1421 body: cst::ast::Expr,
1422) -> Result<(Vec<cst::ast::Param>, cst::ast::Expr), LowerError> {
1423 if params.iter().all(|p| p.opts.is_none()) {
1424 let ps = params
1425 .iter()
1426 .map(|p| Ok(cst::ast::Param::Pat(lower_param_body(&p.body)?)))
1427 .collect::<Result<_, LowerError>>()?;
1428 return Ok((ps, body));
1429 }
1430 let mut chain = body;
1431 for p in params.iter().rev() {
1432 let param_pat = lower_param_body(&p.body)?;
1433 chain = match &p.opts {
1434 Some(opts) => cst::ast::Expr::FunRows {
1435 kw: KwFun(opts.q.0),
1436 opts: lower_opt_binders(opts)?,
1437 param: param_pat,
1438 arrow: ArrowTok(opts.q.0),
1439 body: Box::new(chain),
1440 },
1441 None => cst::ast::Expr::Fun {
1442 kw: KwFun(Span::default()),
1443 params: vec![param_pat],
1444 arrow: ArrowTok(Span::default()),
1445 body: Box::new(chain),
1446 },
1447 };
1448 }
1449 Ok((Vec::new(), chain))
1450}
1451
1452fn lower_command_params(params: &[cst_v1::Param]) -> Result<Vec<cst::ast::Param>, LowerError> {
1465 params
1466 .iter()
1467 .map(|p| match &p.opts {
1468 None => Ok(cst::ast::Param::Pat(lower_param_body(&p.body)?)),
1469 Some(opts) => Ok(cst::ast::Param::Bundled {
1470 opts: lower_opt_binders(opts)?,
1471 body: lower_param_body(&p.body)?,
1472 }),
1473 })
1474 .collect()
1475}
1476
1477fn lower_opt_binders(opts: &ast_v1::OptParamsV1) -> Result<cst::ast::CstOptBinders, LowerError> {
1480 if opts.entries.is_empty() {
1481 return Err(unsupported(
1482 "an empty `?()` optional-parameter bundle",
1483 "a `?(…)` bundle must bind at least one label",
1484 opts.q.0,
1485 ));
1486 }
1487 Ok(cst::ast::CstOptBinders {
1488 q: opts.q.clone(),
1489 paren: clone_paren(&opts.paren),
1490 entries: opts
1491 .entries
1492 .iter()
1493 .map(|e| cst::ast::CstOptBinderEntry {
1494 label: e.label.clone(),
1495 eq: e.eq.clone(),
1496 var: e.var.clone(),
1497 comma: e.comma.clone(),
1498 })
1499 .collect(),
1500 })
1501}
1502
1503fn lower_opt_args(opts: &ast_v1::OptArgsV1) -> Result<cst::ast::CstOptArgs, LowerError> {
1507 if opts.entries.is_empty() {
1508 return Err(unsupported(
1509 "an empty `?()` optional-argument bundle",
1510 "a `?(…)` bundle must supply at least one label",
1511 opts.q.0,
1512 ));
1513 }
1514 Ok(cst::ast::CstOptArgs {
1515 q: opts.q.clone(),
1516 paren: clone_paren(&opts.paren),
1517 entries: opts
1518 .entries
1519 .iter()
1520 .map(|e| {
1521 Ok(cst::ast::CstOptArgEntry {
1522 label: e.label.clone(),
1523 eq: e.eq.clone(),
1524 value: erase_expr(lower_expr(&e.value.0)?),
1525 comma: e.comma.clone(),
1526 })
1527 })
1528 .collect::<Result<_, LowerError>>()?,
1529 })
1530}
1531
1532fn clone_paren(p: &ParenGroup<()>) -> ParenGroup<()> {
1535 ParenGroup {
1536 open: p.open.clone(),
1537 slot: (),
1538 close: p.close.clone(),
1539 }
1540}
1541
1542fn var_tok(name: &str, span: Span) -> VarTok {
1556 VarTok {
1557 name: name.to_string(),
1558 span,
1559 }
1560}
1561
1562fn var_atomic(name: &str, span: Span) -> cst::ast::Atomic {
1563 cst::ast::Atomic::Var(var_tok(name, span))
1564}
1565
1566fn paren_atomic(expr: cst::ast::Expr, span: Span) -> cst::ast::Atomic {
1570 cst::ast::Atomic::Paren {
1571 paren: ParenGroup {
1572 open: LParenTok(span),
1573 slot: (),
1574 close: RParenTok(span),
1575 },
1576 inner: Box::new(cst::ast::ParenBody {
1577 first: cst::ExprErased(Box::new(expr)),
1578 rest: Vec::new(),
1579 }),
1580 }
1581}
1582
1583fn apply_chain(head: cst::ast::Atomic, args: Vec<cst::ast::Atomic>) -> cst::ast::Expr {
1586 let app_args = args
1587 .into_iter()
1588 .map(|atom| cst::ast::AppArg::Atom {
1589 stage: None,
1590 excl: None,
1591 atom,
1592 accesses: Vec::new(),
1593 })
1594 .collect();
1595 cst::ast::Expr::Ops(cst::ast::OpChain {
1596 head: cst::ast::AppExpr {
1597 minus: None,
1598 stage: None,
1599 excl: None,
1600 head,
1601 head_accesses: Vec::new(),
1602 args: app_args,
1603 },
1604 tail: Vec::new(),
1605 before: None,
1606 })
1607}
1608
1609fn fun1(param_name: &str, span: Span, body: cst::ast::Expr) -> cst::ast::Expr {
1611 cst::ast::Expr::Fun {
1612 kw: KwFun(span),
1613 params: vec![cst::ast::PatBot::Var(var_tok(param_name, span))],
1614 arrow: ArrowTok(span),
1615 body: Box::new(body),
1616 }
1617}
1618
1619fn lower_value_math(
1637 kw: &KwVal,
1638 stage: &Option<cst_v1::BindStageV1>,
1639 ctx: &VarTok,
1640 cmd: &AnyHorzCmdTok,
1641 params: &[cst_v1::Param],
1642 scripts: &Option<cst_v1::ScriptsParamV1>,
1643 eq: &DefEqTok,
1644 body: &ast_v1::Expr,
1645) -> Result<cst::TopBinding, LowerError> {
1646 let body = lower_expr(body)?;
1654 let span = eq.0;
1655 let (sub_name, sup_name, wrapped_body) = match scripts {
1656 Some(sp) => (sp.sub.name.clone(), sp.sup.name.clone(), body),
1657 None => (
1658 "%sub".to_string(),
1659 "%sup".to_string(),
1660 apply_chain(
1661 var_atomic("%math-attach-scripts", span),
1662 vec![
1663 var_atomic(&ctx.name, ctx.span),
1664 paren_atomic(body, span),
1665 var_atomic("%sub", span),
1666 var_atomic("%sup", span),
1667 ],
1668 ),
1669 ),
1670 };
1671 let value = fun1(
1672 &ctx.name,
1673 ctx.span,
1674 fun1(&sub_name, span, fun1(&sup_name, span, wrapped_body)),
1675 );
1676 Ok(cst::TopBinding::LetMath {
1677 kw: KwLetMath(kw.0),
1678 stage: stage.as_ref().map(lower_bind_stage),
1679 cmd: plain_horz(cmd)?,
1680 params: lower_command_params(params)?,
1681 eq: eq.clone(),
1682 value,
1683 })
1684}
1685
1686fn lower_expr(e: &ast_v1::Expr) -> Result<cst::ast::Expr, LowerError> {
1689 match e {
1690 ast_v1::Expr::LetRecIn {
1691 let_kw,
1692 first,
1693 ands,
1694 in_kw,
1695 body,
1696 ..
1697 } => Ok(cst::ast::Expr::LetRecIn {
1698 kw: KwLetRec(let_kw.0),
1700 first: lower_rec_clause(first)?,
1701 ands: ands
1702 .iter()
1703 .map(|a| {
1704 Ok(cst::ast::AndBinding {
1705 and_kw: a.and_kw.clone(),
1706 binding: lower_rec_clause(&a.clause)?,
1707 })
1708 })
1709 .collect::<Result<_, LowerError>>()?,
1710 in_kw: in_kw.clone(),
1711 body: Box::new(lower_expr(body)?),
1712 }),
1713 ast_v1::Expr::LetMutableIn {
1714 let_kw,
1715 name,
1716 arrow,
1717 init,
1718 in_kw,
1719 body,
1720 ..
1721 } => Ok(cst::ast::Expr::LetMutableIn {
1722 kw: KwLetMutable(let_kw.0),
1723 name: name.clone(),
1724 arrow: arrow.clone(),
1725 init: Box::new(lower_expr(init)?),
1726 in_kw: in_kw.clone(),
1727 body: Box::new(lower_expr(body)?),
1728 }),
1729 ast_v1::Expr::LetIn {
1730 kw,
1731 name,
1732 params,
1733 eq,
1734 value,
1735 in_kw,
1736 body,
1737 } => {
1738 let (ps, value_expr) = lower_param_units(params, lower_expr(value)?)?;
1739 Ok(cst::ast::Expr::LetIn {
1740 kw: kw.clone(),
1741 name: name.clone(),
1742 ascription: None,
1743 leading_bar: None,
1745 params: ps,
1746 eq: eq.clone(),
1747 value: Box::new(value_expr),
1748 in_kw: in_kw.clone(),
1749 body: Box::new(lower_expr(body)?),
1750 })
1751 }
1752 ast_v1::Expr::LetPatternIn {
1753 kw,
1754 pat,
1755 eq,
1756 value,
1757 in_kw,
1758 body,
1759 } => Ok(cst::ast::Expr::LetPatternIn {
1760 kw: kw.clone(),
1761 pat: erase_pat_non_var(lower_pattern(pat)?),
1762 eq: eq.clone(),
1763 value: Box::new(lower_expr(value)?),
1764 in_kw: in_kw.clone(),
1765 body: Box::new(lower_expr(body)?),
1766 }),
1767 ast_v1::Expr::OpenIn {
1768 open_kw,
1769 name,
1770 in_kw,
1771 body,
1772 ..
1773 } => Ok(cst::ast::Expr::OpenIn {
1774 kw: open_kw.clone(),
1775 name: name.clone(),
1776 in_kw: in_kw.clone(),
1777 body: Box::new(lower_expr(body)?),
1778 }),
1779 ast_v1::Expr::If {
1780 kw,
1781 cond,
1782 then_kw,
1783 then_branch,
1784 else_kw,
1785 else_branch,
1786 } => Ok(cst::ast::Expr::If {
1787 kw: kw.clone(),
1788 cond: Box::new(lower_expr(cond)?),
1789 then_kw: then_kw.clone(),
1790 then_branch: Box::new(lower_expr(then_branch)?),
1791 else_kw: else_kw.clone(),
1792 else_branch: Box::new(lower_expr(else_branch)?),
1793 }),
1794 ast_v1::Expr::Fun {
1795 kw,
1796 params,
1797 arrow,
1798 body,
1799 } => {
1800 let body_expr = lower_expr(body)?;
1801 if params.iter().all(|p| p.opts.is_none()) {
1802 Ok(cst::ast::Expr::Fun {
1803 kw: kw.clone(),
1804 params: params
1805 .iter()
1806 .map(|p| lower_param_body(&p.body))
1807 .collect::<Result<_, _>>()?,
1808 arrow: arrow.clone(),
1809 body: Box::new(body_expr),
1810 })
1811 } else {
1812 let (_, chain) = lower_param_units(params, body_expr)?;
1816 Ok(chain)
1817 }
1818 }
1819 ast_v1::Expr::Match {
1820 kw,
1821 scrutinee,
1822 with_kw,
1823 leading_bar,
1824 first,
1825 rest,
1826 ..
1827 } => Ok(cst::ast::Expr::Match {
1828 kw: kw.clone(),
1829 scrutinee: Box::new(lower_expr(scrutinee)?),
1830 with_kw: with_kw.clone(),
1831 leading_bar: leading_bar.clone(),
1832 first: lower_match_arm(first)?,
1833 rest: rest.iter().map(lower_bar_arm).collect::<Result<_, _>>()?,
1834 }),
1835 ast_v1::Expr::Overwrite { name, arrow, value } => Ok(cst::ast::Expr::Overwrite {
1836 name: name.clone(),
1837 arrow: arrow.clone(),
1838 value: erase_expr(lower_expr(value)?),
1839 }),
1840 ast_v1::Expr::Ops(chain) => Ok(cst::ast::Expr::Ops(lower_op_chain(chain)?)),
1841 }
1842}
1843
1844fn lower_match_arm(a: &ast_v1::MatchArm) -> Result<cst::ast::MatchArm, LowerError> {
1845 Ok(cst::ast::MatchArm {
1846 pat: erase_pat(lower_pattern(&a.pat)?),
1847 guard: None,
1848 arrow: a.arrow.clone(),
1849 body: erase_expr(lower_expr(&a.body)?),
1850 })
1851}
1852
1853fn lower_bar_arm(a: &ast_v1::BarArm) -> Result<cst::ast::BarArm, LowerError> {
1854 Ok(cst::ast::BarArm {
1855 bar: a.bar.clone(),
1856 arm: lower_match_arm(&a.arm)?,
1857 })
1858}
1859
1860fn lower_op_chain(c: &ast_v1::OpChain) -> Result<cst::ast::OpChain, LowerError> {
1861 Ok(cst::ast::OpChain {
1862 head: lower_app_expr(&c.head)?,
1863 tail: c.tail.iter().map(lower_op_rhs).collect::<Result<_, _>>()?,
1864 before: None,
1865 })
1866}
1867
1868fn lower_op_rhs(r: &ast_v1::OpRhs) -> Result<cst::ast::OpRhs, LowerError> {
1869 Ok(cst::ast::OpRhs {
1870 op: r.op.clone(),
1871 rhs: lower_app_expr(&r.rhs)?,
1872 })
1873}
1874
1875fn lower_bind_stage(s: &cst_v1::BindStageV1) -> cst::TopStage {
1880 cst::TopStage {
1881 persistent: s.persistent.clone(),
1882 tilde: s.tilde.clone(),
1883 }
1884}
1885
1886fn lower_stage_prefix(s: &ast_v1::StagePrefix) -> cst::ast::StagePrefix {
1891 match s {
1892 ast_v1::StagePrefix::Next(t) => cst::ast::StagePrefix::Next(t.clone()),
1893 ast_v1::StagePrefix::Prev(t) => cst::ast::StagePrefix::Prev(t.clone()),
1894 }
1895}
1896
1897fn lower_app_expr(e: &ast_v1::AppExpr) -> Result<cst::ast::AppExpr, LowerError> {
1898 Ok(cst::ast::AppExpr {
1899 minus: e.minus.clone(),
1900 stage: e.stage.as_ref().map(lower_stage_prefix),
1901 excl: e.excl.clone(),
1902 head: lower_atomic(&e.head)?,
1903 head_accesses: e.head_accesses.iter().map(lower_access_seg).collect(),
1904 args: e.args.iter().map(lower_app_arg).collect::<Result<_, _>>()?,
1905 })
1906}
1907
1908fn lower_access_seg(a: &ast_v1::AccessSeg) -> cst::ast::AccessSeg {
1909 cst::ast::AccessSeg {
1910 hash: a.hash.clone(),
1911 label: a.label.clone(),
1912 }
1913}
1914
1915fn lower_app_arg(a: &ast_v1::AppArg) -> Result<cst::ast::AppArg, LowerError> {
1916 match a {
1917 ast_v1::AppArg::Bundled {
1920 opts,
1921 excl,
1922 atom,
1923 accesses,
1924 } => Ok(cst::ast::AppArg::Bundled {
1925 opts: lower_opt_args(opts)?,
1926 excl: excl.clone(),
1927 atom: lower_atomic(atom)?,
1928 accesses: accesses.iter().map(lower_access_seg).collect(),
1929 }),
1930 ast_v1::AppArg::BundledCtor { opts, ctor } => Ok(cst::ast::AppArg::BundledCtor {
1931 opts: lower_opt_args(opts)?,
1932 ctor: ctor.clone(),
1933 }),
1934 ast_v1::AppArg::Atom {
1935 stage,
1936 excl,
1937 atom,
1938 accesses,
1939 } => Ok(cst::ast::AppArg::Atom {
1940 stage: stage.as_ref().map(lower_stage_prefix),
1941 excl: excl.clone(),
1942 atom: lower_atomic(atom)?,
1943 accesses: accesses.iter().map(lower_access_seg).collect(),
1944 }),
1945 ast_v1::AppArg::Ctor(t) => Ok(cst::ast::AppArg::Ctor(t.clone())),
1946 }
1947}
1948
1949fn lower_atomic(a: &ast_v1::Atomic) -> Result<cst::ast::Atomic, LowerError> {
1950 match a {
1951 ast_v1::Atomic::Length(t) => Ok(cst::ast::Atomic::Length(t.clone())),
1952 ast_v1::Atomic::Float(t) => Ok(cst::ast::Atomic::Float(t.clone())),
1953 ast_v1::Atomic::Int(t) => Ok(cst::ast::Atomic::Int(t.clone())),
1954 ast_v1::Atomic::Literal(t) => Ok(cst::ast::Atomic::Literal(t.clone())),
1955 ast_v1::Atomic::True(t) => Ok(cst::ast::Atomic::True(t.clone())),
1956 ast_v1::Atomic::False(t) => Ok(cst::ast::Atomic::False(t.clone())),
1957 ast_v1::Atomic::Ctor(t) => Ok(cst::ast::Atomic::Ctor(t.clone())),
1958 ast_v1::Atomic::Var(t) => Ok(cst::ast::Atomic::Var(t.clone())),
1959 ast_v1::Atomic::VarWithMod(t) => Ok(cst::ast::Atomic::VarWithMod(t.clone())),
1960 ast_v1::Atomic::Command { kw, name } => Ok(cst::ast::Atomic::Command {
1961 kw: kw.clone(),
1962 name: name.clone(),
1963 }),
1964 ast_v1::Atomic::Unit { paren } => Ok(cst::ast::Atomic::Unit {
1965 paren: paren.clone(),
1966 }),
1967 ast_v1::Atomic::Paren { paren, inner } => Ok(cst::ast::Atomic::Paren {
1968 paren: paren.clone(),
1969 inner: Box::new(lower_paren_body(inner)?),
1970 }),
1971 ast_v1::Atomic::Record { rec, body } => Ok(cst::ast::Atomic::Record {
1972 rec: rec.clone(),
1973 body: lower_record_body(body)?,
1974 }),
1975 ast_v1::Atomic::List { list, items } => Ok(cst::ast::Atomic::List {
1976 list: list.clone(),
1977 items: items
1978 .iter()
1979 .map(lower_list_item)
1980 .collect::<Result<_, _>>()?,
1981 }),
1982 ast_v1::Atomic::InlineText { igrp, elems } => Ok(cst::ast::Atomic::InlineText {
1983 igrp: igrp.clone(),
1984 elems: elems
1985 .iter()
1986 .map(lower_inline_elem)
1987 .collect::<Result<_, _>>()?,
1988 }),
1989 ast_v1::Atomic::BlockText { bgrp, elems } => Ok(cst::ast::Atomic::BlockText {
1990 bgrp: bgrp.clone(),
1991 elems: elems
1992 .iter()
1993 .map(lower_block_elem)
1994 .collect::<Result<_, _>>()?,
1995 }),
1996 ast_v1::Atomic::MathText { mgrp, elems } => Ok(cst::ast::Atomic::MathText {
2000 mgrp: mgrp.clone(),
2001 elems: lower_math_elems(elems)?,
2002 }),
2003 }
2004}
2005
2006fn lower_record_body(b: &ast_v1::RecordBody) -> Result<cst::ast::RecordBody, LowerError> {
2007 match b {
2008 ast_v1::RecordBody::Update {
2009 base,
2010 with_kw,
2011 fields,
2012 } => Ok(cst::ast::RecordBody::Update {
2013 base: erase_expr(lower_expr(base)?),
2014 with_kw: with_kw.clone(),
2015 fields: fields
2016 .iter()
2017 .map(lower_record_field)
2018 .collect::<Result<_, _>>()?,
2019 }),
2020 ast_v1::RecordBody::Fields(fields) => Ok(cst::ast::RecordBody::Fields(
2021 fields
2022 .iter()
2023 .map(lower_record_field)
2024 .collect::<Result<_, _>>()?,
2025 )),
2026 }
2027}
2028
2029fn lower_record_field(f: &ast_v1::RecordField) -> Result<cst::ast::RecordField, LowerError> {
2030 Ok(cst::ast::RecordField {
2031 name: f.name.clone(),
2032 eq: f.eq.clone(),
2033 value: erase_expr(lower_expr(&f.value)?),
2034 semi: None,
2037 })
2038}
2039
2040fn lower_paren_body(b: &ast_v1::ParenBody) -> Result<cst::ast::ParenBody, LowerError> {
2041 Ok(cst::ast::ParenBody {
2042 first: erase_expr(lower_expr(&b.first)?),
2043 rest: b
2044 .rest
2045 .iter()
2046 .map(lower_comma_expr)
2047 .collect::<Result<_, _>>()?,
2048 })
2049}
2050
2051fn lower_comma_expr(c: &ast_v1::CommaExpr) -> Result<cst::ast::CommaExpr, LowerError> {
2052 Ok(cst::ast::CommaExpr {
2053 comma: c.comma.clone(),
2054 value: erase_expr(lower_expr(&c.value)?),
2055 })
2056}
2057
2058fn lower_list_item(i: &ast_v1::ListItem) -> Result<cst::ast::ListItem, LowerError> {
2059 Ok(cst::ast::ListItem {
2060 value: erase_expr(lower_expr(&i.value)?),
2061 semi: None,
2062 })
2063}
2064
2065fn lower_inline_elem(e: &ast_v1::InlineElem) -> Result<cst::ast::InlineElem, LowerError> {
2066 match e {
2067 ast_v1::InlineElem::Char(t) => Ok(cst::ast::InlineElem::Char(t.clone())),
2068 ast_v1::InlineElem::CodeText(t) => Ok(cst::ast::InlineElem::CodeText(t.clone())),
2069 ast_v1::InlineElem::Space(t) => Ok(cst::ast::InlineElem::Space(t.clone())),
2070 ast_v1::InlineElem::Break(t) => Ok(cst::ast::InlineElem::Break(t.clone())),
2071 ast_v1::InlineElem::Embed { var, semi } => Ok(cst::ast::InlineElem::Embed {
2072 var: var.clone(),
2073 semi: semi.clone(),
2074 }),
2075 ast_v1::InlineElem::EmbedMath { mgrp, elems } => Ok(cst::ast::InlineElem::EmbedMath {
2077 mgrp: mgrp.clone(),
2078 elems: lower_math_elems(elems)?,
2079 }),
2080 ast_v1::InlineElem::Cmd { name, tail } => Ok(cst::ast::InlineElem::Cmd {
2081 name: name.clone(),
2082 tail: lower_cmd_tail(tail)?,
2083 }),
2084 ast_v1::InlineElem::ItemBullet(t) => Ok(cst::ast::InlineElem::ItemBullet(t.clone())),
2085 ast_v1::InlineElem::Sep(t) => Ok(cst::ast::InlineElem::Sep(t.clone())),
2086 }
2087}
2088
2089fn lower_block_elem(e: &ast_v1::BlockElem) -> Result<cst::ast::BlockElem, LowerError> {
2090 match e {
2091 ast_v1::BlockElem::Embed { var, semi } => Ok(cst::ast::BlockElem::Embed {
2092 var: var.clone(),
2093 semi: semi.clone(),
2094 }),
2095 ast_v1::BlockElem::Cmd { name, tail } => Ok(cst::ast::BlockElem::Cmd {
2096 name: name.clone(),
2097 tail: lower_cmd_tail(tail)?,
2098 }),
2099 }
2100}
2101
2102fn lower_cmd_tail(t: &ast_v1::CmdTail) -> Result<cst::ast::CmdTail, LowerError> {
2115 match t {
2116 ast_v1::CmdTail::Semi(s) => Ok(cst::ast::CmdTail::Semi(s.clone())),
2117 ast_v1::CmdTail::Args {
2118 lead_opts,
2119 args,
2120 semi,
2121 } => {
2122 let ast_v1::Expr::Ops(chain) = &*args.0 else {
2123 return Err(unsupported(
2124 "command arguments that are not a plain application chain",
2125 "grammar-drift guard — the cst_v1 grammar cannot actually \
2126 produce this shape in command-tail position",
2127 Span::default(),
2128 ));
2129 };
2130 if !chain.tail.is_empty() || chain.head.minus.is_some() {
2131 return Err(unsupported(
2132 "an operator or unary negation inside a command argument chain",
2133 "grammar-drift guard — the cst_v1 grammar cannot actually \
2134 produce this shape in command-tail position",
2135 Span::default(),
2136 ));
2137 }
2138 let a = &chain.head;
2139 let first = cst::AppArgErased(Box::new(match lead_opts {
2146 Some(opts) => {
2147 if a.stage.is_some() {
2153 return Err(unsupported(
2154 "a staging prefix on a command argument that also \
2155 carries a `?(l = e, …)` bundle",
2156 "the lowered 0.0.6 node has no stage slot on a \
2157 bundled argument; write the `&`/`~` inside the \
2158 parenthesized argument instead",
2159 Span::default(),
2160 ));
2161 }
2162 cst::ast::AppArg::Bundled {
2163 opts: lower_opt_args(opts)?,
2164 excl: a.excl.clone(),
2165 atom: lower_atomic(&a.head)?,
2166 accesses: a.head_accesses.iter().map(lower_access_seg).collect(),
2167 }
2168 }
2169 None => cst::ast::AppArg::Atom {
2170 stage: a.stage.as_ref().map(lower_stage_prefix),
2171 excl: a.excl.clone(),
2172 atom: lower_atomic(&a.head)?,
2173 accesses: a.head_accesses.iter().map(lower_access_seg).collect(),
2174 },
2175 }));
2176 let rest = a
2177 .args
2178 .iter()
2179 .map(|arg| Ok(cst::AppArgErased(Box::new(lower_app_arg(arg)?))))
2180 .collect::<Result<Vec<_>, LowerError>>()?;
2181 Ok(cst::ast::CmdTail::Args {
2182 first,
2183 rest,
2184 semi: semi.clone(),
2185 })
2186 }
2187 }
2188}
2189
2190fn lower_math_elems(elems: &[cst_v1::MathErasedV1]) -> Result<Vec<cst::MathErased>, LowerError> {
2202 elems
2203 .iter()
2204 .map(|e| Ok(cst::MathErased(Box::new(lower_math_elem_cst(e)?))))
2205 .collect()
2206}
2207
2208fn lower_math_elem_cst(m: &ast_v1::MathElemCst) -> Result<cst::ast::MathElemCst, LowerError> {
2209 Ok(cst::ast::MathElemCst {
2210 base: lower_math_bot(&m.base)?,
2211 scripts: m
2212 .scripts
2213 .iter()
2214 .map(lower_math_script)
2215 .collect::<Result<_, _>>()?,
2216 })
2217}
2218
2219fn lower_math_bot(b: &ast_v1::MathBot) -> Result<cst::ast::MathBot, LowerError> {
2220 Ok(match b {
2221 ast_v1::MathBot::Cmd { name, args } => cst::ast::MathBot::Cmd {
2222 name: name.clone(),
2229 args: args.iter().map(lower_math_arg).collect::<Result<_, _>>()?,
2230 },
2231 ast_v1::MathBot::Chars(t) => cst::ast::MathBot::Chars(t.clone()),
2232 ast_v1::MathBot::Embed(t) => cst::ast::MathBot::Embed(t.clone()),
2233 ast_v1::MathBot::Sep(t) => cst::ast::MathBot::Sep(t.clone()),
2234 ast_v1::MathBot::Group { mgrp, elems } => cst::ast::MathBot::Group {
2235 mgrp: mgrp.clone(),
2236 elems: lower_math_elems(elems)?,
2237 },
2238 })
2239}
2240
2241fn lower_math_script(s: &ast_v1::MathScript) -> Result<cst::ast::MathScript, LowerError> {
2242 Ok(match s {
2243 ast_v1::MathScript::Super { hat, group } => cst::ast::MathScript::Super {
2244 hat: hat.clone(),
2245 group: lower_math_group_arg(group)?,
2246 },
2247 ast_v1::MathScript::Sub { under, group } => cst::ast::MathScript::Sub {
2248 under: under.clone(),
2249 group: lower_math_group_arg(group)?,
2250 },
2251 ast_v1::MathScript::Primes(t) => cst::ast::MathScript::Primes(t.clone()),
2252 })
2253}
2254
2255fn lower_math_group_arg(g: &ast_v1::MathGroupArg) -> Result<cst::ast::MathGroupArg, LowerError> {
2256 Ok(match g {
2257 ast_v1::MathGroupArg::Group { mgrp, elems } => cst::ast::MathGroupArg::Group {
2258 mgrp: mgrp.clone(),
2259 elems: lower_math_elems(elems)?,
2260 },
2261 ast_v1::MathGroupArg::Bot(b) => cst::ast::MathGroupArg::Bot(Box::new(lower_math_bot(b)?)),
2262 })
2263}
2264
2265fn lower_math_arg(a: &ast_v1::MathArg) -> Result<cst::ast::MathArg, LowerError> {
2266 Ok(cst::ast::MathArg::Plain(match a {
2267 ast_v1::MathArg::Math { mgrp, elems } => cst::ast::MathArgBody::Math {
2268 mgrp: mgrp.clone(),
2269 elems: lower_math_elems(elems)?,
2270 },
2271 ast_v1::MathArg::Inline { igrp, elems } => cst::ast::MathArgBody::Inline {
2272 igrp: igrp.clone(),
2273 elems: elems
2274 .iter()
2275 .map(lower_inline_elem)
2276 .collect::<Result<_, _>>()?,
2277 },
2278 ast_v1::MathArg::Block { bgrp, elems } => cst::ast::MathArgBody::Block {
2279 bgrp: bgrp.clone(),
2280 elems: elems
2281 .iter()
2282 .map(lower_block_elem)
2283 .collect::<Result<_, _>>()?,
2284 },
2285 ast_v1::MathArg::ParenEscape { paren, inner } => cst::ast::MathArgBody::ParenEscape {
2286 paren: paren.clone(),
2287 inner: Box::new(lower_paren_body(inner)?),
2288 },
2289 ast_v1::MathArg::ListEscape { list, items } => cst::ast::MathArgBody::ListEscape {
2290 list: list.clone(),
2291 items: items
2292 .iter()
2293 .map(lower_list_item)
2294 .collect::<Result<_, _>>()?,
2295 },
2296 ast_v1::MathArg::RecordEscape { rec, body } => cst::ast::MathArgBody::RecordEscape {
2297 rec: rec.clone(),
2298 body: lower_record_body(body)?,
2299 },
2300 }))
2301}
2302
2303fn lower_pattern(p: &ast_v1::Pattern) -> Result<cst::ast::Pattern, LowerError> {
2306 Ok(cst::ast::Pattern {
2307 head: lower_pat_cons(&p.head)?,
2308 as_clause: p.as_clause.as_ref().map(lower_as_clause),
2309 })
2310}
2311
2312fn lower_as_clause(a: &ast_v1::AsClause) -> cst::ast::AsClause {
2313 cst::ast::AsClause {
2314 as_kw: a.as_kw.clone(),
2315 name: a.name.clone(),
2316 }
2317}
2318
2319fn lower_pat_cons(c: &ast_v1::PatCons) -> Result<cst::ast::PatCons, LowerError> {
2320 Ok(cst::ast::PatCons {
2321 head: lower_pat_bot(&c.head)?,
2322 tail: c
2323 .tail
2324 .iter()
2325 .map(lower_cons_seg)
2326 .collect::<Result<_, _>>()?,
2327 })
2328}
2329
2330fn lower_cons_seg(s: &ast_v1::ConsSeg) -> Result<cst::ast::ConsSeg, LowerError> {
2331 Ok(cst::ast::ConsSeg {
2332 cons: s.cons.clone(),
2333 tail: lower_pat_bot(&s.tail)?,
2334 })
2335}
2336
2337fn lower_pat_bot(p: &ast_v1::PatBot) -> Result<cst::ast::PatBot, LowerError> {
2338 match p {
2339 ast_v1::PatBot::CtorApplied { ctor, arg } => Ok(cst::ast::PatBot::CtorApplied {
2340 ctor: ctor.clone(),
2341 arg: Box::new(lower_pat_bot(arg)?),
2342 }),
2343 ast_v1::PatBot::Ctor(t) => Ok(cst::ast::PatBot::Ctor(t.clone())),
2344 ast_v1::PatBot::Int(t) => Ok(cst::ast::PatBot::Int(t.clone())),
2345 ast_v1::PatBot::True(t) => Ok(cst::ast::PatBot::True(t.clone())),
2346 ast_v1::PatBot::False(t) => Ok(cst::ast::PatBot::False(t.clone())),
2347 ast_v1::PatBot::Str(t) => Ok(cst::ast::PatBot::Str(t.clone())),
2348 ast_v1::PatBot::Wild(t) => Ok(cst::ast::PatBot::Wild(t.clone())),
2349 ast_v1::PatBot::Var(t) => Ok(cst::ast::PatBot::Var(t.clone())),
2350 ast_v1::PatBot::Unit { paren } => Ok(cst::ast::PatBot::Unit {
2351 paren: paren.clone(),
2352 }),
2353 ast_v1::PatBot::Paren { paren, inner } => Ok(cst::ast::PatBot::Paren {
2354 paren: paren.clone(),
2355 inner: Box::new(lower_pattern_paren_body(inner)?),
2356 }),
2357 ast_v1::PatBot::List { plist, items } => Ok(cst::ast::PatBot::List {
2358 plist: plist.clone(),
2359 items: items
2360 .iter()
2361 .map(lower_pat_list_item)
2362 .collect::<Result<_, _>>()?,
2363 }),
2364 }
2365}
2366
2367fn lower_param_body(pb: &ast_v1::ParamBody) -> Result<cst::ast::PatBot, LowerError> {
2378 match pb {
2379 ast_v1::ParamBody::Pat(p) => lower_pat_bot(p),
2380 ast_v1::ParamBody::Ascribed { paren, inner } => Ok(cst::ast::PatBot::Paren {
2381 paren: paren.clone(),
2382 inner: Box::new(cst::ast::PatternParenBody {
2383 first: erase_pat(lower_pattern(&inner.pat)?),
2384 rest: Vec::new(),
2385 }),
2386 }),
2387 }
2388}
2389
2390fn lower_pattern_paren_body(
2391 b: &ast_v1::PatternParenBody,
2392) -> Result<cst::ast::PatternParenBody, LowerError> {
2393 Ok(cst::ast::PatternParenBody {
2394 first: erase_pat(lower_pattern(&b.first)?),
2395 rest: b
2396 .rest
2397 .iter()
2398 .map(lower_comma_pattern)
2399 .collect::<Result<_, _>>()?,
2400 })
2401}
2402
2403fn lower_comma_pattern(c: &ast_v1::CommaPattern) -> Result<cst::ast::CommaPattern, LowerError> {
2404 Ok(cst::ast::CommaPattern {
2405 comma: c.comma.clone(),
2406 value: erase_pat(lower_pattern(&c.value)?),
2407 })
2408}
2409
2410fn lower_pat_list_item(i: &ast_v1::PatListItem) -> Result<cst::ast::PatListItem, LowerError> {
2411 Ok(cst::ast::PatListItem {
2412 value: erase_pat(lower_pattern(&i.value)?),
2413 semi: None,
2414 })
2415}
2416
2417fn erase_expr(e: cst::ast::Expr) -> cst::ExprErased {
2420 cst::ExprErased(Box::new(e))
2421}
2422
2423fn erase_pat(p: cst::ast::Pattern) -> cst::PatErased {
2424 cst::PatErased(Box::new(p))
2425}
2426
2427fn erase_pat_non_var(p: cst::ast::Pattern) -> cst::PatNonVarErased {
2438 debug_assert!(
2439 !p.is_bare_var(),
2440 "a destructuring `let`'s target lowered to a bare variable"
2441 );
2442 cst::PatNonVarErased(Box::new(p))
2443}
2444
2445#[cfg(test)]
2446mod tests {
2447 use super::*;
2448
2449 fn parse_v1(src: &str) -> cst_v1::FileV1 {
2450 rustyfi_syntax::parse_file_v1(src).unwrap_or_else(|e| panic!("v1 parse failed: {e}"))
2451 }
2452
2453 #[test]
2458 fn let_rec_document_lowers_with_and_chain() {
2459 let file = parse_v1(
2460 "let rec even n = if n <= 0 then true else odd (n - 1)\n\
2461 and odd n = if n <= 0 then false else even (n - 1) in even 4",
2462 );
2463 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2464 let cst::ast::Expr::LetRecIn { first, ands, .. } = ast else {
2465 panic!("expected Expr::LetRecIn");
2466 };
2467 assert_eq!(ands.len(), 1, "one `and` continuation");
2468 assert!(first.ascription.is_none());
2469 assert!(first.leading_bar.is_none());
2470 assert!(first.extra.is_empty());
2471 assert_eq!(first.name.name, "even");
2472 assert_eq!(ands[0].binding.name.name, "odd");
2473 }
2474
2475 #[test]
2478 fn val_rec_library_lowers_to_top_binding_letrec() {
2479 let file = parse_v1(
2480 "module M = struct\n\
2481 val rec even n = odd n\n\
2482 and odd n = even n\n\
2483 end",
2484 );
2485 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2486 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2487 panic!("expected a TopBinding::Module");
2488 };
2489 assert_eq!(decls.len(), 1);
2490 let cst::TopBinding::LetRec { first, ands, .. } = &*decls[0].0 else {
2491 panic!("expected TopBinding::LetRec, got {:?}", decls[0].0);
2492 };
2493 assert_eq!(first.name.name, "even");
2494 assert_eq!(ands.len(), 1);
2495 assert_eq!(ands[0].binding.name.name, "odd");
2496 }
2497
2498 #[test]
2500 fn val_mutable_lowers_to_top_binding_letmutable() {
2501 let file = parse_v1("module M = struct\nval mutable c <- 0\nend");
2502 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2503 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2504 panic!("expected a TopBinding::Module");
2505 };
2506 assert_eq!(decls.len(), 1);
2507 assert!(
2508 matches!(&*decls[0].0, cst::TopBinding::LetMutable { name, .. } if name.name == "c"),
2509 "{:?}",
2510 decls[0].0
2511 );
2512 }
2513
2514 #[test]
2516 fn let_mutable_in_document_lowers_to_expr_letmutablein() {
2517 let file = parse_v1("let mutable c <- 0 in c <- !c + 1");
2518 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2519 assert!(
2520 matches!(&ast, cst::ast::Expr::LetMutableIn { name, .. } if name.name == "c"),
2521 "{ast:?}"
2522 );
2523 }
2524
2525 #[test]
2530 fn type_and_chain_inside_module_qualifies_names_and_synonym_reference() {
2531 let file = parse_v1(
2532 "module M = struct\n\
2533 type t = int\n\
2534 and u = t\n\
2535 end",
2536 );
2537 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2538 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2539 panic!("expected a TopBinding::Module");
2540 };
2541 assert_eq!(
2542 decls.len(),
2543 2,
2544 "an `and`-chain lowers to N consecutive Type decls"
2545 );
2546 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2547 panic!("expected decls[0] to be a Type decl");
2548 };
2549 assert_eq!(t_decl.name.name, "M.t");
2550 let cst::TopBinding::Type(u_decl) = &*decls[1].0 else {
2551 panic!("expected decls[1] to be a Type decl");
2552 };
2553 assert_eq!(u_decl.name.name, "M.u");
2554 let cst::TypeDeclBody::Synonym(ty) = &u_decl.body else {
2555 panic!("expected a synonym body");
2556 };
2557 let cst::ast::TypeExpr::Atom(prod) = ty else {
2558 panic!("expected a bare TypeProd (no arrow)");
2559 };
2560 let cst::ast::TypeApp {
2561 head: cst::ast::TypeAtom::Name(n),
2562 ..
2563 } = &prod.first
2564 else {
2565 panic!("expected a bare type name atom");
2566 };
2567 assert_eq!(
2568 n.name, "M.t",
2569 "u's synonym body must reference the QUALIFIED t"
2570 );
2571 }
2572
2573 #[test]
2578 fn nested_module_type_reference_qualifies_to_outer_path() {
2579 let file = parse_v1(
2580 "module M = struct\n\
2581 type t = int\n\
2582 module N = struct\n\
2583 type u = t\n\
2584 end\n\
2585 end",
2586 );
2587 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2588 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2589 panic!("expected a TopBinding::Module");
2590 };
2591 assert_eq!(decls.len(), 2);
2592 let cst::TopBinding::Module {
2593 name: inner_name,
2594 decls: inner_decls,
2595 ..
2596 } = &*decls[1].0
2597 else {
2598 panic!("expected decls[1] to be a nested TopBinding::Module");
2599 };
2600 assert_eq!(inner_name.name, "N");
2601 assert_eq!(inner_decls.len(), 1);
2602 let cst::TopBinding::Type(u_decl) = &*inner_decls[0].0 else {
2603 panic!("expected a Type decl");
2604 };
2605 assert_eq!(u_decl.name.name, "M.N.u");
2606 let cst::TypeDeclBody::Synonym(ty) = &u_decl.body else {
2607 panic!("expected a synonym body");
2608 };
2609 let cst::ast::TypeExpr::Atom(prod) = ty else {
2610 panic!("expected a bare TypeProd");
2611 };
2612 let cst::ast::TypeApp {
2613 head: cst::ast::TypeAtom::Name(n),
2614 ..
2615 } = &prod.first
2616 else {
2617 panic!("expected a bare type name atom");
2618 };
2619 assert_eq!(
2620 n.name, "M.t",
2621 "the outer M.t must stay visible/qualified inside N"
2622 );
2623 }
2624
2625 #[test]
2629 fn type_app_prefix_to_postfix_bridge() {
2630 let file = parse_v1("module M = struct\ntype t = option int\nend");
2631 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2632 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2633 panic!("expected a TopBinding::Module");
2634 };
2635 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2636 panic!("expected a Type decl");
2637 };
2638 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2639 panic!("expected a synonym body");
2640 };
2641 let cst::ast::TypeExpr::Atom(prod) = ty else {
2642 panic!("expected a bare TypeProd");
2643 };
2644 assert!(prod.first.rest.len() == 1, "{:?}", prod.first);
2646 assert!(
2647 matches!(&prod.first.rest[0], cst::ast::TypeAtom::Name(n) if n.name == "option"),
2648 "{:?}",
2649 prod.first.rest[0]
2650 );
2651 assert!(
2652 matches!(&prod.first.head, cst::ast::TypeAtom::Name(n) if n.name == "int"),
2653 "{:?}",
2654 prod.first.head
2655 );
2656 }
2657
2658 #[test]
2662 fn type_app_arity_2_lowers_to_nary_atom_run() {
2663 let file = parse_v1("module M = struct\ntype t = pair int int\nend");
2664 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2665 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2666 panic!("expected a TopBinding::Module");
2667 };
2668 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2669 panic!("expected a Type decl");
2670 };
2671 let cst::TypeDeclBody::Synonym(cst::ast::TypeExpr::Atom(prod)) = &t_decl.body else {
2672 panic!("expected a bare synonym TypeProd");
2673 };
2674 assert!(
2676 matches!(&prod.first.head, cst::ast::TypeAtom::Name(n) if n.name == "int"),
2677 "{:?}",
2678 prod.first.head
2679 );
2680 assert_eq!(prod.first.rest.len(), 2, "{:?}", prod.first.rest);
2681 assert!(matches!(&prod.first.rest[0], cst::ast::TypeAtom::Name(n) if n.name == "int"));
2682 assert!(matches!(&prod.first.rest[1], cst::ast::TypeAtom::Name(n) if n.name == "pair"));
2683 }
2684
2685 #[test]
2691 fn mutual_variant_pair_lowers_to_two_type_decls() {
2692 let file = parse_v1(
2693 "module M = struct\n\
2694 type a = A of b\n\
2695 and b = B of a\n\
2696 end",
2697 );
2698 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2699 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2700 panic!("expected a TopBinding::Module");
2701 };
2702 assert_eq!(decls.len(), 2);
2703 assert!(matches!(&*decls[0].0, cst::TopBinding::Type(d) if d.name.name == "M.a"));
2704 assert!(matches!(&*decls[1].0, cst::TopBinding::Type(d) if d.name.name == "M.b"));
2705 }
2706
2707 #[test]
2712 fn type_record_lowers_to_cst_record_atom() {
2713 let file = parse_v1("module M = struct\ntype t = (| x : int, y : bool |)\nend");
2714 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2715 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2716 panic!("expected a TopBinding::Module");
2717 };
2718 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2719 panic!("expected a Type decl");
2720 };
2721 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2722 panic!("expected a synonym body");
2723 };
2724 let cst::ast::TypeExpr::Atom(prod) = ty else {
2725 panic!("expected a bare TypeProd");
2726 };
2727 let cst::ast::TypeApp {
2728 head: cst::ast::TypeAtom::Record { fields, .. },
2729 ..
2730 } = &prod.first
2731 else {
2732 panic!("expected TypeAtom::Record, got {:?}", prod.first);
2733 };
2734 assert_eq!(fields.len(), 2);
2735 assert_eq!(fields[0].name.name, "x");
2736 assert!(
2737 matches!(&*fields[0].ty.0, cst::ast::TypeExpr::Atom(p)
2738 if matches!(&p.first, cst::ast::TypeApp { head: cst::ast::TypeAtom::Name(n), .. } if n.name == "int")),
2739 "{:?}",
2740 fields[0].ty.0
2741 );
2742 assert_eq!(fields[1].name.name, "y");
2743 assert!(
2744 matches!(&*fields[1].ty.0, cst::ast::TypeExpr::Atom(p)
2745 if matches!(&p.first, cst::ast::TypeApp { head: cst::ast::TypeAtom::Name(n), .. } if n.name == "bool")),
2746 "{:?}",
2747 fields[1].ty.0
2748 );
2749 }
2750
2751 #[test]
2756 fn type_record_field_type_is_qualified_like_any_other_type_position() {
2757 let file = parse_v1(
2758 "module M = struct\n\
2759 type config = int\n\
2760 type t = (| c : config |)\n\
2761 end",
2762 );
2763 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2764 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2765 panic!("expected a TopBinding::Module");
2766 };
2767 assert_eq!(decls.len(), 2);
2768 let cst::TopBinding::Type(t_decl) = &*decls[1].0 else {
2769 panic!("expected decls[1] to be a Type decl");
2770 };
2771 assert_eq!(t_decl.name.name, "M.t");
2772 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2773 panic!("expected a synonym body");
2774 };
2775 let cst::ast::TypeExpr::Atom(prod) = ty else {
2776 panic!("expected a bare TypeProd");
2777 };
2778 let cst::ast::TypeApp {
2779 head: cst::ast::TypeAtom::Record { fields, .. },
2780 ..
2781 } = &prod.first
2782 else {
2783 panic!("expected TypeAtom::Record, got {:?}", prod.first);
2784 };
2785 assert_eq!(fields.len(), 1);
2786 assert_eq!(fields[0].name.name, "c");
2787 let cst::ast::TypeExpr::Atom(field_prod) = &*fields[0].ty.0 else {
2788 panic!("expected a bare TypeProd for the field type");
2789 };
2790 let cst::ast::TypeApp {
2791 head: cst::ast::TypeAtom::Name(n),
2792 ..
2793 } = &field_prod.first
2794 else {
2795 panic!("expected a bare type name atom, got {:?}", field_prod.first);
2796 };
2797 assert_eq!(
2798 n.name, "M.config",
2799 "the field's bare `config` must qualify to M.config"
2800 );
2801 }
2802
2803 #[test]
2809 fn old_optional_sigils_no_longer_parse() {
2810 assert!(rustyfi_syntax::parse_file_v1("f ?:1").is_err());
2811 assert!(rustyfi_syntax::parse_file_v1("f ?*").is_err());
2812 }
2813
2814 #[test]
2817 fn empty_opt_arg_bundle_is_a_lower_error() {
2818 let file = parse_v1("f ?() x");
2819 let err = lower_document_v1(&file).unwrap_err();
2820 assert!(
2821 err.to_string().contains("optional-argument bundle"),
2822 "{err}"
2823 );
2824 }
2825
2826 #[test]
2831 fn math_text_lowers_structurally() {
2832 let file = parse_v1("${x}");
2833 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2834 let cst::ast::Expr::Ops(chain) = &ast else {
2835 panic!("expected Expr::Ops, got {ast:?}");
2836 };
2837 let cst::ast::Atomic::MathText { elems, .. } = &chain.head.head else {
2838 panic!("expected Atomic::MathText, got {:?}", chain.head.head);
2839 };
2840 assert_eq!(elems.len(), 1, "one math element (`x`)");
2841 let cst::ast::MathBot::Chars(t) = &elems[0].base else {
2842 panic!("expected MathBot::Chars, got {:?}", elems[0].base);
2843 };
2844 assert_eq!(t.text, "x");
2845 }
2846
2847 #[test]
2859 fn mod_qualified_command_name_in_bind_is_a_lower_error() {
2860 let tok = HorzCmdWithModTok {
2861 mods: vec!["Mod".to_string()],
2862 name: "\\emph".to_string(),
2863 span: Span::default(),
2864 };
2865 let err = plain_horz(&AnyHorzCmdTok::Mod(tok)).unwrap_err();
2866 assert!(err.to_string().contains("module-qualified"), "{err}");
2867
2868 let tok = VertCmdWithModTok {
2869 mods: vec!["Mod".to_string()],
2870 name: "+p".to_string(),
2871 span: Span::default(),
2872 };
2873 let err = plain_vert(&AnyVertCmdTok::Mod(tok)).unwrap_err();
2874 assert!(err.to_string().contains("module-qualified"), "{err}");
2875 }
2876
2877 #[test]
2878 fn lower_file_v1_on_a_document_is_an_error_not_a_panic() {
2879 let file = parse_v1("3");
2880 assert!(lower_file_v1(&file).is_err());
2881 }
2882
2883 #[test]
2884 fn lower_document_v1_on_a_library_is_an_error_not_a_panic() {
2885 let file = parse_v1("module M = struct\nval x = 1\nend");
2886 assert!(lower_document_v1(&file).is_err());
2887 }
2888
2889 #[test]
2893 fn lower_file_v1_yields_one_real_module_binding() {
2894 let file = parse_v1(
2895 "module V01Mini = struct\n\
2896 val x = 1\n\
2897 val y = 2\n\
2898 end",
2899 );
2900 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2901 assert_eq!(
2902 lowered.len(),
2903 1,
2904 "one TopBinding::Module, not spliced binds"
2905 );
2906 let cst::TopBinding::Module {
2907 name, sig, decls, ..
2908 } = &lowered[0]
2909 else {
2910 panic!("expected a TopBinding::Module, got {:?}", lowered[0]);
2911 };
2912 assert_eq!(name.name, "V01Mini");
2913 assert!(sig.is_none(), "no signature annotation in Sub-slice 2a");
2914 assert_eq!(decls.len(), 2);
2915 }
2916
2917 #[test]
2920 fn lower_file_v1_nested_module_bind_lowers_to_nested_module() {
2921 let file = parse_v1(
2922 "module M = struct\n\
2923 val x = 1\n\
2924 module N = struct\n\
2925 val y = 2\n\
2926 end\n\
2927 end",
2928 );
2929 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2930 assert_eq!(lowered.len(), 1);
2931 let cst::TopBinding::Module { name, decls, .. } = &lowered[0] else {
2932 panic!("expected a TopBinding::Module");
2933 };
2934 assert_eq!(name.name, "M");
2935 assert_eq!(decls.len(), 2);
2936 assert!(matches!(&*decls[0].0, cst::TopBinding::Let(_)));
2937 let cst::TopBinding::Module {
2938 name: inner_name,
2939 sig: inner_sig,
2940 decls: inner_decls,
2941 ..
2942 } = &*decls[1].0
2943 else {
2944 panic!("expected decls[1] to be a nested TopBinding::Module");
2945 };
2946 assert_eq!(inner_name.name, "N");
2947 assert!(inner_sig.is_none());
2948 assert_eq!(inner_decls.len(), 1);
2949 }
2950
2951 #[test]
2958 fn sig_annot_on_library_lowers_like_its_unsealed_twin() {
2959 let sealed = parse_v1("module M :> sig val x : int end = struct\nval x = 1\nend");
2960 let unsealed = parse_v1("module M = struct\nval x = 1\nend");
2961 let sealed_lowered = lower_file_v1(&sealed).unwrap_or_else(|e| panic!("sealed: {e}"));
2962 let unsealed_lowered = lower_file_v1(&unsealed).unwrap_or_else(|e| panic!("unsealed: {e}"));
2963 assert_eq!(sealed_lowered.len(), 1);
2964 assert_eq!(unsealed_lowered.len(), 1);
2965 let cst::TopBinding::Module {
2966 sig: sealed_sig,
2967 decls: sealed_decls,
2968 ..
2969 } = &sealed_lowered[0]
2970 else {
2971 panic!("expected a TopBinding::Module");
2972 };
2973 let cst::TopBinding::Module {
2974 sig: unsealed_sig,
2975 decls: unsealed_decls,
2976 ..
2977 } = &unsealed_lowered[0]
2978 else {
2979 panic!("expected a TopBinding::Module");
2980 };
2981 assert!(
2982 sealed_sig.is_none(),
2983 "the seal must lower to NO cst::SigAnnot at all"
2984 );
2985 assert!(unsealed_sig.is_none());
2986 assert_eq!(sealed_decls.len(), unsealed_decls.len());
2987 assert!(matches!(&*sealed_decls[0].0, cst::TopBinding::Let(_)));
2988 assert!(matches!(&*unsealed_decls[0].0, cst::TopBinding::Let(_)));
2989 }
2990
2991 #[test]
2997 fn sig_annot_body_error_is_identical_with_or_without_a_seal() {
2998 let sealed = parse_v1("module M :> sig end = struct\ninclude struct val x = 1 end\nend");
2999 let unsealed = parse_v1("module M = struct\ninclude struct val x = 1 end\nend");
3000 let sealed_err = lower_file_v1(&sealed).unwrap_err();
3001 let unsealed_err = lower_file_v1(&unsealed).unwrap_err();
3002 assert!(
3003 sealed_err.to_string().contains("inline `struct"),
3004 "{sealed_err}"
3005 );
3006 assert_eq!(sealed_err.construct, unsealed_err.construct);
3007 assert_eq!(sealed_err.hint, unsealed_err.hint);
3008 }
3009
3010 #[test]
3015 fn nested_module_sig_annot_lowers_like_its_unsealed_twin() {
3016 let sealed = parse_v1(
3017 "module M = struct\n\
3018 module N :> sig val y : int end = struct\n\
3019 val y = 2\n\
3020 end\n\
3021 end",
3022 );
3023 let unsealed = parse_v1(
3024 "module M = struct\n\
3025 module N = struct\n\
3026 val y = 2\n\
3027 end\n\
3028 end",
3029 );
3030 let sealed_lowered = lower_file_v1(&sealed).unwrap_or_else(|e| panic!("sealed: {e}"));
3031 let unsealed_lowered = lower_file_v1(&unsealed).unwrap_or_else(|e| panic!("unsealed: {e}"));
3032 let cst::TopBinding::Module {
3033 decls: sealed_decls,
3034 ..
3035 } = &sealed_lowered[0]
3036 else {
3037 panic!("expected a TopBinding::Module");
3038 };
3039 let cst::TopBinding::Module {
3040 decls: unsealed_decls,
3041 ..
3042 } = &unsealed_lowered[0]
3043 else {
3044 panic!("expected a TopBinding::Module");
3045 };
3046 assert_eq!(sealed_decls.len(), 1);
3047 assert_eq!(unsealed_decls.len(), 1);
3048 let cst::TopBinding::Module {
3049 name: sealed_name,
3050 sig: sealed_sig,
3051 decls: sealed_inner,
3052 ..
3053 } = &*sealed_decls[0].0
3054 else {
3055 panic!("expected decls[0] to be a nested TopBinding::Module");
3056 };
3057 let cst::TopBinding::Module {
3058 name: unsealed_name,
3059 sig: unsealed_sig,
3060 decls: unsealed_inner,
3061 ..
3062 } = &*unsealed_decls[0].0
3063 else {
3064 panic!("expected decls[0] to be a nested TopBinding::Module");
3065 };
3066 assert_eq!(sealed_name.name, "N");
3067 assert_eq!(unsealed_name.name, "N");
3068 assert!(
3069 sealed_sig.is_none(),
3070 "the seal must lower to NO cst::SigAnnot at all"
3071 );
3072 assert!(unsealed_sig.is_none());
3073 assert_eq!(sealed_inner.len(), unsealed_inner.len());
3074 }
3075
3076 #[test]
3080 fn module_alias_with_unknown_target_is_a_lower_error() {
3081 let file = parse_v1("module M = struct\nmodule P = N\nend");
3082 let err = lower_file_v1(&file).unwrap_err();
3083 assert!(err.to_string().contains("module alias"), "{err}");
3084 }
3085
3086 #[test]
3090 fn module_alias_to_a_real_target_lowers_member_copies() {
3091 let file = parse_v1(
3092 "module M = struct\n\
3093 module Base = struct val x = 1 val f y = y end\n\
3094 module Alias = Base\n\
3095 end",
3096 );
3097 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3098 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3099 panic!("expected a TopBinding::Module");
3100 };
3101 let cst::TopBinding::Module {
3102 name,
3103 decls: alias_decls,
3104 ..
3105 } = &*decls[1].0
3106 else {
3107 panic!("expected decls[1] to be the Alias module");
3108 };
3109 assert_eq!(name.name, "Alias");
3110 assert_eq!(alias_decls.len(), 2, "one copy per exported value member");
3111 for (decl, expected_name) in alias_decls.iter().zip(["x", "f"]) {
3112 let cst::TopBinding::Let(top_let) = &*decl.0 else {
3113 panic!("expected a TopBinding::Let copy");
3114 };
3115 assert_eq!(top_let.name.name, expected_name);
3116 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3117 panic!("expected an application-chain expression");
3118 };
3119 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3120 panic!("expected a VarWithMod reference to the target");
3121 };
3122 assert_eq!(tok.mods, vec!["M.Base".to_string()]);
3123 assert_eq!(tok.name, expected_name);
3124 }
3125 }
3126
3127 #[test]
3132 fn module_alias_forward_reference_is_a_lower_error() {
3133 let file = parse_v1(
3134 "module M = struct\n\
3135 module Early = Later\n\
3136 module Later = struct val x = 1 end\n\
3137 end",
3138 );
3139 let err = lower_file_v1(&file).unwrap_err();
3140 assert!(err.to_string().contains("module alias"), "{err}");
3141 }
3142
3143 #[test]
3145 fn functor_application_is_a_lower_error() {
3146 let file = parse_v1("module M = struct\nmodule P = F X\nend");
3147 let err = lower_file_v1(&file).unwrap_err();
3148 assert!(err.to_string().contains("functor application"), "{err}");
3149 }
3150
3151 #[test]
3156 fn functor_literal_emits_zero_runtime_bindings() {
3157 let file = parse_v1(
3158 "module M = struct\n\
3159 module F = fun (X : sig val x : int end) -> struct val y = X.x end\n\
3160 end",
3161 );
3162 let bindings =
3163 lower_file_v1(&file).expect("a functor definition now lowers, emitting no member");
3164 let cst::TopBinding::Module { decls, .. } = &bindings[0] else {
3165 panic!("expected M's TopBinding::Module")
3166 };
3167 assert!(
3168 decls.is_empty(),
3169 "a functor literal contributes no decls: {decls:?}"
3170 );
3171 }
3172
3173 #[test]
3177 fn module_coercion_with_unknown_target_is_a_lower_error() {
3178 let file = parse_v1("module M = struct\nmodule P = N :> S\nend");
3179 let err = lower_file_v1(&file).unwrap_err();
3180 assert!(err.to_string().contains("module alias"), "{err}");
3181 }
3182
3183 #[test]
3192 fn module_coercion_lowers_the_same_copies_as_its_uncoerced_twin() {
3193 fn alias_copy_names(src: &str) -> (Vec<String>, Vec<Vec<String>>) {
3194 let lowered = lower_file_v1(&parse_v1(src)).unwrap_or_else(|e| panic!("{e}"));
3195 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3196 panic!("expected a TopBinding::Module");
3197 };
3198 let cst::TopBinding::Module {
3199 name,
3200 decls: alias_decls,
3201 ..
3202 } = &*decls[1].0
3203 else {
3204 panic!("expected decls[1] to be the Alias module");
3205 };
3206 assert_eq!(name.name, "Alias");
3207 let mut names = Vec::new();
3208 let mut refs = Vec::new();
3209 for d in alias_decls {
3210 let cst::TopBinding::Let(top_let) = &*d.0 else {
3211 panic!("expected a Let copy");
3212 };
3213 names.push(top_let.name.name.clone());
3214 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3215 panic!("expected an application chain");
3216 };
3217 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3218 panic!("expected a VarWithMod reference");
3219 };
3220 refs.push(tok.mods.clone());
3221 }
3222 (names, refs)
3223 }
3224 let bare = alias_copy_names(
3225 "module M = struct\n\
3226 module Base = struct val x = 1 end\n\
3227 module Alias = Base\n\
3228 end",
3229 );
3230 let coerced = alias_copy_names(
3231 "module M = struct\n\
3232 module Base = struct val x = 1 end\n\
3233 module Alias = Base :> sig val x : int end\n\
3234 end",
3235 );
3236 assert_eq!(bare, coerced);
3237 assert_eq!(bare.0, vec!["x".to_string()]);
3238 assert_eq!(bare.1, vec![vec!["M.Base".to_string()]]);
3239 }
3240
3241 #[test]
3244 fn signature_bind_lowers_to_nothing() {
3245 let file = parse_v1("module M = struct\nsignature S = sig end\nval x = 1\nend");
3246 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3247 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3248 panic!("expected a TopBinding::Module");
3249 };
3250 assert_eq!(decls.len(), 1, "the signature bind contributes zero decls");
3251 assert!(matches!(&*decls[0].0, cst::TopBinding::Let(_)));
3252 }
3253
3254 #[test]
3257 fn include_of_an_unknown_module_is_a_lower_error() {
3258 let file = parse_v1("module M = struct\ninclude N\nend");
3259 let err = lower_file_v1(&file).unwrap_err();
3260 let msg = err.to_string();
3261 assert!(msg.contains("include"), "{msg}");
3262 assert!(msg.contains("unknown module"), "{msg}");
3263 }
3264
3265 #[test]
3270 fn include_of_a_real_target_splices_member_copies_unwrapped() {
3271 let file = parse_v1(
3272 "module M = struct\n\
3273 module Base = struct val x = 1 val f y = y end\n\
3274 include Base\n\
3275 end",
3276 );
3277 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3278 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3279 panic!("expected a TopBinding::Module");
3280 };
3281 assert_eq!(decls.len(), 3, "Base + 2 spliced copies, unwrapped");
3284 for (decl, expected_name) in decls[1..].iter().zip(["x", "f"]) {
3285 let cst::TopBinding::Let(top_let) = &*decl.0 else {
3286 panic!("expected a TopBinding::Let copy, got {:?}", decl.0);
3287 };
3288 assert_eq!(top_let.name.name, expected_name);
3289 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3290 panic!("expected an application-chain expression");
3291 };
3292 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3293 panic!("expected a VarWithMod reference to the target");
3294 };
3295 assert_eq!(tok.mods, vec!["M.Base".to_string()]);
3296 assert_eq!(tok.name, expected_name);
3297 }
3298 }
3299
3300 #[test]
3305 fn include_forward_reference_is_a_lower_error() {
3306 let file = parse_v1(
3307 "module M = struct\n\
3308 include Later\n\
3309 module Later = struct val x = 1 end\n\
3310 end",
3311 );
3312 let err = lower_file_v1(&file).unwrap_err();
3313 assert!(err.to_string().contains("unknown module"), "{err}");
3314 }
3315
3316 #[test]
3320 fn include_of_a_functor_application_is_the_2f_functor_error() {
3321 let file = parse_v1("module M = struct\nmodule F = struct end\ninclude F X\nend");
3327 let err = lower_file_v1(&file).unwrap_err();
3328 let msg = err.to_string();
3329 assert!(msg.contains("functor application"), "{msg}");
3330 assert!(msg.contains("unknown"), "{msg}");
3331 }
3332
3333 #[test]
3336 fn include_of_an_inline_struct_literal_is_a_lower_error() {
3337 let file = parse_v1("module M = struct\ninclude struct val x = 1 end\nend");
3338 let err = lower_file_v1(&file).unwrap_err();
3339 let msg = err.to_string();
3340 assert!(msg.contains("struct"), "{msg}");
3341 assert!(msg.contains("name the module first"), "{msg}");
3342 }
3343
3344 #[test]
3347 fn include_of_a_coerced_module_is_a_lower_error() {
3348 let file = parse_v1(
3349 "module M = struct\n\
3350 module Base = struct val x = 1 end\n\
3351 include Base :> sig val x : int end\n\
3352 end",
3353 );
3354 let err = lower_file_v1(&file).unwrap_err();
3355 let msg = err.to_string();
3356 assert!(msg.contains("coerced module"), "{msg}");
3357 }
3358
3359 #[test]
3364 fn self_include_is_a_lower_error() {
3365 let file = parse_v1("module P = struct\ninclude P\nend");
3366 let err = lower_file_v1(&file).unwrap_err();
3367 assert!(err.to_string().contains("unknown module"), "{err}");
3368 }
3369
3370 #[test]
3375 fn cmd_tail_bridge_matches_flat_app_arg_shape() {
3376 let file = parse_v1(r"{\cmd{a}{b}}");
3377 let cst_v1::FileV1::Document { body, .. } = file else {
3378 panic!("expected a document file");
3379 };
3380 let ast_v1::Expr::Ops(chain) = body else {
3381 panic!("expected an operator-chain expression");
3382 };
3383 let ast_v1::Atomic::InlineText { elems, .. } = chain.head.head else {
3384 panic!("expected inline text");
3385 };
3386 let ast_v1::InlineElem::Cmd { tail, .. } = &elems[0] else {
3387 panic!("expected the first element to be a command");
3388 };
3389 let lowered = lower_cmd_tail(tail).unwrap();
3390 let cst::ast::CmdTail::Args { first, rest, .. } = lowered else {
3391 panic!("expected CmdTail::Args");
3392 };
3393 assert_eq!(
3394 rest.len(),
3395 1,
3396 "\\cmd{{a}}{{b}} has exactly one trailing arg"
3397 );
3398 assert!(matches!(
3399 &*first.0,
3400 cst::ast::AppArg::Atom {
3401 stage: None,
3402 atom: cst::ast::Atomic::InlineText { .. },
3403 ..
3404 }
3405 ));
3406 assert!(matches!(
3407 &*rest[0].0,
3408 cst::ast::AppArg::Atom {
3409 stage: None,
3410 atom: cst::ast::Atomic::InlineText { .. },
3411 ..
3412 }
3413 ));
3414 }
3415}