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(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
2427#[cfg(test)]
2428mod tests {
2429 use super::*;
2430
2431 fn parse_v1(src: &str) -> cst_v1::FileV1 {
2432 rustyfi_syntax::parse_file_v1(src).unwrap_or_else(|e| panic!("v1 parse failed: {e}"))
2433 }
2434
2435 #[test]
2440 fn let_rec_document_lowers_with_and_chain() {
2441 let file = parse_v1(
2442 "let rec even n = if n <= 0 then true else odd (n - 1)\n\
2443 and odd n = if n <= 0 then false else even (n - 1) in even 4",
2444 );
2445 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2446 let cst::ast::Expr::LetRecIn { first, ands, .. } = ast else {
2447 panic!("expected Expr::LetRecIn");
2448 };
2449 assert_eq!(ands.len(), 1, "one `and` continuation");
2450 assert!(first.ascription.is_none());
2451 assert!(first.leading_bar.is_none());
2452 assert!(first.extra.is_empty());
2453 assert_eq!(first.name.name, "even");
2454 assert_eq!(ands[0].binding.name.name, "odd");
2455 }
2456
2457 #[test]
2460 fn val_rec_library_lowers_to_top_binding_letrec() {
2461 let file = parse_v1(
2462 "module M = struct\n\
2463 val rec even n = odd n\n\
2464 and odd n = even n\n\
2465 end",
2466 );
2467 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2468 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2469 panic!("expected a TopBinding::Module");
2470 };
2471 assert_eq!(decls.len(), 1);
2472 let cst::TopBinding::LetRec { first, ands, .. } = &*decls[0].0 else {
2473 panic!("expected TopBinding::LetRec, got {:?}", decls[0].0);
2474 };
2475 assert_eq!(first.name.name, "even");
2476 assert_eq!(ands.len(), 1);
2477 assert_eq!(ands[0].binding.name.name, "odd");
2478 }
2479
2480 #[test]
2482 fn val_mutable_lowers_to_top_binding_letmutable() {
2483 let file = parse_v1("module M = struct\nval mutable c <- 0\nend");
2484 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2485 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2486 panic!("expected a TopBinding::Module");
2487 };
2488 assert_eq!(decls.len(), 1);
2489 assert!(
2490 matches!(&*decls[0].0, cst::TopBinding::LetMutable { name, .. } if name.name == "c"),
2491 "{:?}",
2492 decls[0].0
2493 );
2494 }
2495
2496 #[test]
2498 fn let_mutable_in_document_lowers_to_expr_letmutablein() {
2499 let file = parse_v1("let mutable c <- 0 in c <- !c + 1");
2500 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2501 assert!(
2502 matches!(&ast, cst::ast::Expr::LetMutableIn { name, .. } if name.name == "c"),
2503 "{ast:?}"
2504 );
2505 }
2506
2507 #[test]
2512 fn type_and_chain_inside_module_qualifies_names_and_synonym_reference() {
2513 let file = parse_v1(
2514 "module M = struct\n\
2515 type t = int\n\
2516 and u = t\n\
2517 end",
2518 );
2519 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2520 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2521 panic!("expected a TopBinding::Module");
2522 };
2523 assert_eq!(
2524 decls.len(),
2525 2,
2526 "an `and`-chain lowers to N consecutive Type decls"
2527 );
2528 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2529 panic!("expected decls[0] to be a Type decl");
2530 };
2531 assert_eq!(t_decl.name.name, "M.t");
2532 let cst::TopBinding::Type(u_decl) = &*decls[1].0 else {
2533 panic!("expected decls[1] to be a Type decl");
2534 };
2535 assert_eq!(u_decl.name.name, "M.u");
2536 let cst::TypeDeclBody::Synonym(ty) = &u_decl.body else {
2537 panic!("expected a synonym body");
2538 };
2539 let cst::ast::TypeExpr::Atom(prod) = ty else {
2540 panic!("expected a bare TypeProd (no arrow)");
2541 };
2542 let cst::ast::TypeApp {
2543 head: cst::ast::TypeAtom::Name(n),
2544 ..
2545 } = &prod.first
2546 else {
2547 panic!("expected a bare type name atom");
2548 };
2549 assert_eq!(
2550 n.name, "M.t",
2551 "u's synonym body must reference the QUALIFIED t"
2552 );
2553 }
2554
2555 #[test]
2560 fn nested_module_type_reference_qualifies_to_outer_path() {
2561 let file = parse_v1(
2562 "module M = struct\n\
2563 type t = int\n\
2564 module N = struct\n\
2565 type u = t\n\
2566 end\n\
2567 end",
2568 );
2569 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2570 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2571 panic!("expected a TopBinding::Module");
2572 };
2573 assert_eq!(decls.len(), 2);
2574 let cst::TopBinding::Module {
2575 name: inner_name,
2576 decls: inner_decls,
2577 ..
2578 } = &*decls[1].0
2579 else {
2580 panic!("expected decls[1] to be a nested TopBinding::Module");
2581 };
2582 assert_eq!(inner_name.name, "N");
2583 assert_eq!(inner_decls.len(), 1);
2584 let cst::TopBinding::Type(u_decl) = &*inner_decls[0].0 else {
2585 panic!("expected a Type decl");
2586 };
2587 assert_eq!(u_decl.name.name, "M.N.u");
2588 let cst::TypeDeclBody::Synonym(ty) = &u_decl.body else {
2589 panic!("expected a synonym body");
2590 };
2591 let cst::ast::TypeExpr::Atom(prod) = ty else {
2592 panic!("expected a bare TypeProd");
2593 };
2594 let cst::ast::TypeApp {
2595 head: cst::ast::TypeAtom::Name(n),
2596 ..
2597 } = &prod.first
2598 else {
2599 panic!("expected a bare type name atom");
2600 };
2601 assert_eq!(
2602 n.name, "M.t",
2603 "the outer M.t must stay visible/qualified inside N"
2604 );
2605 }
2606
2607 #[test]
2611 fn type_app_prefix_to_postfix_bridge() {
2612 let file = parse_v1("module M = struct\ntype t = option int\nend");
2613 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2614 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2615 panic!("expected a TopBinding::Module");
2616 };
2617 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2618 panic!("expected a Type decl");
2619 };
2620 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2621 panic!("expected a synonym body");
2622 };
2623 let cst::ast::TypeExpr::Atom(prod) = ty else {
2624 panic!("expected a bare TypeProd");
2625 };
2626 assert!(prod.first.rest.len() == 1, "{:?}", prod.first);
2628 assert!(
2629 matches!(&prod.first.rest[0], cst::ast::TypeAtom::Name(n) if n.name == "option"),
2630 "{:?}",
2631 prod.first.rest[0]
2632 );
2633 assert!(
2634 matches!(&prod.first.head, cst::ast::TypeAtom::Name(n) if n.name == "int"),
2635 "{:?}",
2636 prod.first.head
2637 );
2638 }
2639
2640 #[test]
2644 fn type_app_arity_2_lowers_to_nary_atom_run() {
2645 let file = parse_v1("module M = struct\ntype t = pair int int\nend");
2646 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2647 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2648 panic!("expected a TopBinding::Module");
2649 };
2650 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2651 panic!("expected a Type decl");
2652 };
2653 let cst::TypeDeclBody::Synonym(cst::ast::TypeExpr::Atom(prod)) = &t_decl.body else {
2654 panic!("expected a bare synonym TypeProd");
2655 };
2656 assert!(
2658 matches!(&prod.first.head, cst::ast::TypeAtom::Name(n) if n.name == "int"),
2659 "{:?}",
2660 prod.first.head
2661 );
2662 assert_eq!(prod.first.rest.len(), 2, "{:?}", prod.first.rest);
2663 assert!(matches!(&prod.first.rest[0], cst::ast::TypeAtom::Name(n) if n.name == "int"));
2664 assert!(matches!(&prod.first.rest[1], cst::ast::TypeAtom::Name(n) if n.name == "pair"));
2665 }
2666
2667 #[test]
2673 fn mutual_variant_pair_lowers_to_two_type_decls() {
2674 let file = parse_v1(
2675 "module M = struct\n\
2676 type a = A of b\n\
2677 and b = B of a\n\
2678 end",
2679 );
2680 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2681 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2682 panic!("expected a TopBinding::Module");
2683 };
2684 assert_eq!(decls.len(), 2);
2685 assert!(matches!(&*decls[0].0, cst::TopBinding::Type(d) if d.name.name == "M.a"));
2686 assert!(matches!(&*decls[1].0, cst::TopBinding::Type(d) if d.name.name == "M.b"));
2687 }
2688
2689 #[test]
2694 fn type_record_lowers_to_cst_record_atom() {
2695 let file = parse_v1("module M = struct\ntype t = (| x : int, y : bool |)\nend");
2696 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2697 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2698 panic!("expected a TopBinding::Module");
2699 };
2700 let cst::TopBinding::Type(t_decl) = &*decls[0].0 else {
2701 panic!("expected a Type decl");
2702 };
2703 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2704 panic!("expected a synonym body");
2705 };
2706 let cst::ast::TypeExpr::Atom(prod) = ty else {
2707 panic!("expected a bare TypeProd");
2708 };
2709 let cst::ast::TypeApp {
2710 head: cst::ast::TypeAtom::Record { fields, .. },
2711 ..
2712 } = &prod.first
2713 else {
2714 panic!("expected TypeAtom::Record, got {:?}", prod.first);
2715 };
2716 assert_eq!(fields.len(), 2);
2717 assert_eq!(fields[0].name.name, "x");
2718 assert!(
2719 matches!(&*fields[0].ty.0, cst::ast::TypeExpr::Atom(p)
2720 if matches!(&p.first, cst::ast::TypeApp { head: cst::ast::TypeAtom::Name(n), .. } if n.name == "int")),
2721 "{:?}",
2722 fields[0].ty.0
2723 );
2724 assert_eq!(fields[1].name.name, "y");
2725 assert!(
2726 matches!(&*fields[1].ty.0, cst::ast::TypeExpr::Atom(p)
2727 if matches!(&p.first, cst::ast::TypeApp { head: cst::ast::TypeAtom::Name(n), .. } if n.name == "bool")),
2728 "{:?}",
2729 fields[1].ty.0
2730 );
2731 }
2732
2733 #[test]
2738 fn type_record_field_type_is_qualified_like_any_other_type_position() {
2739 let file = parse_v1(
2740 "module M = struct\n\
2741 type config = int\n\
2742 type t = (| c : config |)\n\
2743 end",
2744 );
2745 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2746 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
2747 panic!("expected a TopBinding::Module");
2748 };
2749 assert_eq!(decls.len(), 2);
2750 let cst::TopBinding::Type(t_decl) = &*decls[1].0 else {
2751 panic!("expected decls[1] to be a Type decl");
2752 };
2753 assert_eq!(t_decl.name.name, "M.t");
2754 let cst::TypeDeclBody::Synonym(ty) = &t_decl.body else {
2755 panic!("expected a synonym body");
2756 };
2757 let cst::ast::TypeExpr::Atom(prod) = ty else {
2758 panic!("expected a bare TypeProd");
2759 };
2760 let cst::ast::TypeApp {
2761 head: cst::ast::TypeAtom::Record { fields, .. },
2762 ..
2763 } = &prod.first
2764 else {
2765 panic!("expected TypeAtom::Record, got {:?}", prod.first);
2766 };
2767 assert_eq!(fields.len(), 1);
2768 assert_eq!(fields[0].name.name, "c");
2769 let cst::ast::TypeExpr::Atom(field_prod) = &*fields[0].ty.0 else {
2770 panic!("expected a bare TypeProd for the field type");
2771 };
2772 let cst::ast::TypeApp {
2773 head: cst::ast::TypeAtom::Name(n),
2774 ..
2775 } = &field_prod.first
2776 else {
2777 panic!("expected a bare type name atom, got {:?}", field_prod.first);
2778 };
2779 assert_eq!(
2780 n.name, "M.config",
2781 "the field's bare `config` must qualify to M.config"
2782 );
2783 }
2784
2785 #[test]
2791 fn old_optional_sigils_no_longer_parse() {
2792 assert!(rustyfi_syntax::parse_file_v1("f ?:1").is_err());
2793 assert!(rustyfi_syntax::parse_file_v1("f ?*").is_err());
2794 }
2795
2796 #[test]
2799 fn empty_opt_arg_bundle_is_a_lower_error() {
2800 let file = parse_v1("f ?() x");
2801 let err = lower_document_v1(&file).unwrap_err();
2802 assert!(
2803 err.to_string().contains("optional-argument bundle"),
2804 "{err}"
2805 );
2806 }
2807
2808 #[test]
2813 fn math_text_lowers_structurally() {
2814 let file = parse_v1("${x}");
2815 let ast = lower_document_v1(&file).unwrap_or_else(|e| panic!("lower_document_v1: {e}"));
2816 let cst::ast::Expr::Ops(chain) = &ast else {
2817 panic!("expected Expr::Ops, got {ast:?}");
2818 };
2819 let cst::ast::Atomic::MathText { elems, .. } = &chain.head.head else {
2820 panic!("expected Atomic::MathText, got {:?}", chain.head.head);
2821 };
2822 assert_eq!(elems.len(), 1, "one math element (`x`)");
2823 let cst::ast::MathBot::Chars(t) = &elems[0].base else {
2824 panic!("expected MathBot::Chars, got {:?}", elems[0].base);
2825 };
2826 assert_eq!(t.text, "x");
2827 }
2828
2829 #[test]
2841 fn mod_qualified_command_name_in_bind_is_a_lower_error() {
2842 let tok = HorzCmdWithModTok {
2843 mods: vec!["Mod".to_string()],
2844 name: "\\emph".to_string(),
2845 span: Span::default(),
2846 };
2847 let err = plain_horz(&AnyHorzCmdTok::Mod(tok)).unwrap_err();
2848 assert!(err.to_string().contains("module-qualified"), "{err}");
2849
2850 let tok = VertCmdWithModTok {
2851 mods: vec!["Mod".to_string()],
2852 name: "+p".to_string(),
2853 span: Span::default(),
2854 };
2855 let err = plain_vert(&AnyVertCmdTok::Mod(tok)).unwrap_err();
2856 assert!(err.to_string().contains("module-qualified"), "{err}");
2857 }
2858
2859 #[test]
2860 fn lower_file_v1_on_a_document_is_an_error_not_a_panic() {
2861 let file = parse_v1("3");
2862 assert!(lower_file_v1(&file).is_err());
2863 }
2864
2865 #[test]
2866 fn lower_document_v1_on_a_library_is_an_error_not_a_panic() {
2867 let file = parse_v1("module M = struct\nval x = 1\nend");
2868 assert!(lower_document_v1(&file).is_err());
2869 }
2870
2871 #[test]
2875 fn lower_file_v1_yields_one_real_module_binding() {
2876 let file = parse_v1(
2877 "module V01Mini = struct\n\
2878 val x = 1\n\
2879 val y = 2\n\
2880 end",
2881 );
2882 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2883 assert_eq!(
2884 lowered.len(),
2885 1,
2886 "one TopBinding::Module, not spliced binds"
2887 );
2888 let cst::TopBinding::Module {
2889 name, sig, decls, ..
2890 } = &lowered[0]
2891 else {
2892 panic!("expected a TopBinding::Module, got {:?}", lowered[0]);
2893 };
2894 assert_eq!(name.name, "V01Mini");
2895 assert!(sig.is_none(), "no signature annotation in Sub-slice 2a");
2896 assert_eq!(decls.len(), 2);
2897 }
2898
2899 #[test]
2902 fn lower_file_v1_nested_module_bind_lowers_to_nested_module() {
2903 let file = parse_v1(
2904 "module M = struct\n\
2905 val x = 1\n\
2906 module N = struct\n\
2907 val y = 2\n\
2908 end\n\
2909 end",
2910 );
2911 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
2912 assert_eq!(lowered.len(), 1);
2913 let cst::TopBinding::Module { name, decls, .. } = &lowered[0] else {
2914 panic!("expected a TopBinding::Module");
2915 };
2916 assert_eq!(name.name, "M");
2917 assert_eq!(decls.len(), 2);
2918 assert!(matches!(&*decls[0].0, cst::TopBinding::Let(_)));
2919 let cst::TopBinding::Module {
2920 name: inner_name,
2921 sig: inner_sig,
2922 decls: inner_decls,
2923 ..
2924 } = &*decls[1].0
2925 else {
2926 panic!("expected decls[1] to be a nested TopBinding::Module");
2927 };
2928 assert_eq!(inner_name.name, "N");
2929 assert!(inner_sig.is_none());
2930 assert_eq!(inner_decls.len(), 1);
2931 }
2932
2933 #[test]
2940 fn sig_annot_on_library_lowers_like_its_unsealed_twin() {
2941 let sealed = parse_v1("module M :> sig val x : int end = struct\nval x = 1\nend");
2942 let unsealed = parse_v1("module M = struct\nval x = 1\nend");
2943 let sealed_lowered = lower_file_v1(&sealed).unwrap_or_else(|e| panic!("sealed: {e}"));
2944 let unsealed_lowered = lower_file_v1(&unsealed).unwrap_or_else(|e| panic!("unsealed: {e}"));
2945 assert_eq!(sealed_lowered.len(), 1);
2946 assert_eq!(unsealed_lowered.len(), 1);
2947 let cst::TopBinding::Module {
2948 sig: sealed_sig,
2949 decls: sealed_decls,
2950 ..
2951 } = &sealed_lowered[0]
2952 else {
2953 panic!("expected a TopBinding::Module");
2954 };
2955 let cst::TopBinding::Module {
2956 sig: unsealed_sig,
2957 decls: unsealed_decls,
2958 ..
2959 } = &unsealed_lowered[0]
2960 else {
2961 panic!("expected a TopBinding::Module");
2962 };
2963 assert!(
2964 sealed_sig.is_none(),
2965 "the seal must lower to NO cst::SigAnnot at all"
2966 );
2967 assert!(unsealed_sig.is_none());
2968 assert_eq!(sealed_decls.len(), unsealed_decls.len());
2969 assert!(matches!(&*sealed_decls[0].0, cst::TopBinding::Let(_)));
2970 assert!(matches!(&*unsealed_decls[0].0, cst::TopBinding::Let(_)));
2971 }
2972
2973 #[test]
2979 fn sig_annot_body_error_is_identical_with_or_without_a_seal() {
2980 let sealed = parse_v1("module M :> sig end = struct\ninclude struct val x = 1 end\nend");
2981 let unsealed = parse_v1("module M = struct\ninclude struct val x = 1 end\nend");
2982 let sealed_err = lower_file_v1(&sealed).unwrap_err();
2983 let unsealed_err = lower_file_v1(&unsealed).unwrap_err();
2984 assert!(
2985 sealed_err.to_string().contains("inline `struct"),
2986 "{sealed_err}"
2987 );
2988 assert_eq!(sealed_err.construct, unsealed_err.construct);
2989 assert_eq!(sealed_err.hint, unsealed_err.hint);
2990 }
2991
2992 #[test]
2997 fn nested_module_sig_annot_lowers_like_its_unsealed_twin() {
2998 let sealed = parse_v1(
2999 "module M = struct\n\
3000 module N :> sig val y : int end = struct\n\
3001 val y = 2\n\
3002 end\n\
3003 end",
3004 );
3005 let unsealed = parse_v1(
3006 "module M = struct\n\
3007 module N = struct\n\
3008 val y = 2\n\
3009 end\n\
3010 end",
3011 );
3012 let sealed_lowered = lower_file_v1(&sealed).unwrap_or_else(|e| panic!("sealed: {e}"));
3013 let unsealed_lowered = lower_file_v1(&unsealed).unwrap_or_else(|e| panic!("unsealed: {e}"));
3014 let cst::TopBinding::Module {
3015 decls: sealed_decls,
3016 ..
3017 } = &sealed_lowered[0]
3018 else {
3019 panic!("expected a TopBinding::Module");
3020 };
3021 let cst::TopBinding::Module {
3022 decls: unsealed_decls,
3023 ..
3024 } = &unsealed_lowered[0]
3025 else {
3026 panic!("expected a TopBinding::Module");
3027 };
3028 assert_eq!(sealed_decls.len(), 1);
3029 assert_eq!(unsealed_decls.len(), 1);
3030 let cst::TopBinding::Module {
3031 name: sealed_name,
3032 sig: sealed_sig,
3033 decls: sealed_inner,
3034 ..
3035 } = &*sealed_decls[0].0
3036 else {
3037 panic!("expected decls[0] to be a nested TopBinding::Module");
3038 };
3039 let cst::TopBinding::Module {
3040 name: unsealed_name,
3041 sig: unsealed_sig,
3042 decls: unsealed_inner,
3043 ..
3044 } = &*unsealed_decls[0].0
3045 else {
3046 panic!("expected decls[0] to be a nested TopBinding::Module");
3047 };
3048 assert_eq!(sealed_name.name, "N");
3049 assert_eq!(unsealed_name.name, "N");
3050 assert!(
3051 sealed_sig.is_none(),
3052 "the seal must lower to NO cst::SigAnnot at all"
3053 );
3054 assert!(unsealed_sig.is_none());
3055 assert_eq!(sealed_inner.len(), unsealed_inner.len());
3056 }
3057
3058 #[test]
3062 fn module_alias_with_unknown_target_is_a_lower_error() {
3063 let file = parse_v1("module M = struct\nmodule P = N\nend");
3064 let err = lower_file_v1(&file).unwrap_err();
3065 assert!(err.to_string().contains("module alias"), "{err}");
3066 }
3067
3068 #[test]
3072 fn module_alias_to_a_real_target_lowers_member_copies() {
3073 let file = parse_v1(
3074 "module M = struct\n\
3075 module Base = struct val x = 1 val f y = y end\n\
3076 module Alias = Base\n\
3077 end",
3078 );
3079 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3080 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3081 panic!("expected a TopBinding::Module");
3082 };
3083 let cst::TopBinding::Module {
3084 name,
3085 decls: alias_decls,
3086 ..
3087 } = &*decls[1].0
3088 else {
3089 panic!("expected decls[1] to be the Alias module");
3090 };
3091 assert_eq!(name.name, "Alias");
3092 assert_eq!(alias_decls.len(), 2, "one copy per exported value member");
3093 for (decl, expected_name) in alias_decls.iter().zip(["x", "f"]) {
3094 let cst::TopBinding::Let(top_let) = &*decl.0 else {
3095 panic!("expected a TopBinding::Let copy");
3096 };
3097 assert_eq!(top_let.name.name, expected_name);
3098 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3099 panic!("expected an application-chain expression");
3100 };
3101 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3102 panic!("expected a VarWithMod reference to the target");
3103 };
3104 assert_eq!(tok.mods, vec!["M.Base".to_string()]);
3105 assert_eq!(tok.name, expected_name);
3106 }
3107 }
3108
3109 #[test]
3114 fn module_alias_forward_reference_is_a_lower_error() {
3115 let file = parse_v1(
3116 "module M = struct\n\
3117 module Early = Later\n\
3118 module Later = struct val x = 1 end\n\
3119 end",
3120 );
3121 let err = lower_file_v1(&file).unwrap_err();
3122 assert!(err.to_string().contains("module alias"), "{err}");
3123 }
3124
3125 #[test]
3127 fn functor_application_is_a_lower_error() {
3128 let file = parse_v1("module M = struct\nmodule P = F X\nend");
3129 let err = lower_file_v1(&file).unwrap_err();
3130 assert!(err.to_string().contains("functor application"), "{err}");
3131 }
3132
3133 #[test]
3138 fn functor_literal_emits_zero_runtime_bindings() {
3139 let file = parse_v1(
3140 "module M = struct\n\
3141 module F = fun (X : sig val x : int end) -> struct val y = X.x end\n\
3142 end",
3143 );
3144 let bindings =
3145 lower_file_v1(&file).expect("a functor definition now lowers, emitting no member");
3146 let cst::TopBinding::Module { decls, .. } = &bindings[0] else {
3147 panic!("expected M's TopBinding::Module")
3148 };
3149 assert!(
3150 decls.is_empty(),
3151 "a functor literal contributes no decls: {decls:?}"
3152 );
3153 }
3154
3155 #[test]
3159 fn module_coercion_with_unknown_target_is_a_lower_error() {
3160 let file = parse_v1("module M = struct\nmodule P = N :> S\nend");
3161 let err = lower_file_v1(&file).unwrap_err();
3162 assert!(err.to_string().contains("module alias"), "{err}");
3163 }
3164
3165 #[test]
3174 fn module_coercion_lowers_the_same_copies_as_its_uncoerced_twin() {
3175 fn alias_copy_names(src: &str) -> (Vec<String>, Vec<Vec<String>>) {
3176 let lowered = lower_file_v1(&parse_v1(src)).unwrap_or_else(|e| panic!("{e}"));
3177 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3178 panic!("expected a TopBinding::Module");
3179 };
3180 let cst::TopBinding::Module {
3181 name,
3182 decls: alias_decls,
3183 ..
3184 } = &*decls[1].0
3185 else {
3186 panic!("expected decls[1] to be the Alias module");
3187 };
3188 assert_eq!(name.name, "Alias");
3189 let mut names = Vec::new();
3190 let mut refs = Vec::new();
3191 for d in alias_decls {
3192 let cst::TopBinding::Let(top_let) = &*d.0 else {
3193 panic!("expected a Let copy");
3194 };
3195 names.push(top_let.name.name.clone());
3196 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3197 panic!("expected an application chain");
3198 };
3199 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3200 panic!("expected a VarWithMod reference");
3201 };
3202 refs.push(tok.mods.clone());
3203 }
3204 (names, refs)
3205 }
3206 let bare = alias_copy_names(
3207 "module M = struct\n\
3208 module Base = struct val x = 1 end\n\
3209 module Alias = Base\n\
3210 end",
3211 );
3212 let coerced = alias_copy_names(
3213 "module M = struct\n\
3214 module Base = struct val x = 1 end\n\
3215 module Alias = Base :> sig val x : int end\n\
3216 end",
3217 );
3218 assert_eq!(bare, coerced);
3219 assert_eq!(bare.0, vec!["x".to_string()]);
3220 assert_eq!(bare.1, vec![vec!["M.Base".to_string()]]);
3221 }
3222
3223 #[test]
3226 fn signature_bind_lowers_to_nothing() {
3227 let file = parse_v1("module M = struct\nsignature S = sig end\nval x = 1\nend");
3228 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3229 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3230 panic!("expected a TopBinding::Module");
3231 };
3232 assert_eq!(decls.len(), 1, "the signature bind contributes zero decls");
3233 assert!(matches!(&*decls[0].0, cst::TopBinding::Let(_)));
3234 }
3235
3236 #[test]
3239 fn include_of_an_unknown_module_is_a_lower_error() {
3240 let file = parse_v1("module M = struct\ninclude N\nend");
3241 let err = lower_file_v1(&file).unwrap_err();
3242 let msg = err.to_string();
3243 assert!(msg.contains("include"), "{msg}");
3244 assert!(msg.contains("unknown module"), "{msg}");
3245 }
3246
3247 #[test]
3252 fn include_of_a_real_target_splices_member_copies_unwrapped() {
3253 let file = parse_v1(
3254 "module M = struct\n\
3255 module Base = struct val x = 1 val f y = y end\n\
3256 include Base\n\
3257 end",
3258 );
3259 let lowered = lower_file_v1(&file).unwrap_or_else(|e| panic!("lower_file_v1: {e}"));
3260 let cst::TopBinding::Module { decls, .. } = &lowered[0] else {
3261 panic!("expected a TopBinding::Module");
3262 };
3263 assert_eq!(decls.len(), 3, "Base + 2 spliced copies, unwrapped");
3266 for (decl, expected_name) in decls[1..].iter().zip(["x", "f"]) {
3267 let cst::TopBinding::Let(top_let) = &*decl.0 else {
3268 panic!("expected a TopBinding::Let copy, got {:?}", decl.0);
3269 };
3270 assert_eq!(top_let.name.name, expected_name);
3271 let cst::ast::Expr::Ops(chain) = &top_let.value else {
3272 panic!("expected an application-chain expression");
3273 };
3274 let cst::ast::Atomic::VarWithMod(tok) = &chain.head.head else {
3275 panic!("expected a VarWithMod reference to the target");
3276 };
3277 assert_eq!(tok.mods, vec!["M.Base".to_string()]);
3278 assert_eq!(tok.name, expected_name);
3279 }
3280 }
3281
3282 #[test]
3287 fn include_forward_reference_is_a_lower_error() {
3288 let file = parse_v1(
3289 "module M = struct\n\
3290 include Later\n\
3291 module Later = struct val x = 1 end\n\
3292 end",
3293 );
3294 let err = lower_file_v1(&file).unwrap_err();
3295 assert!(err.to_string().contains("unknown module"), "{err}");
3296 }
3297
3298 #[test]
3302 fn include_of_a_functor_application_is_the_2f_functor_error() {
3303 let file = parse_v1("module M = struct\nmodule F = struct end\ninclude F X\nend");
3309 let err = lower_file_v1(&file).unwrap_err();
3310 let msg = err.to_string();
3311 assert!(msg.contains("functor application"), "{msg}");
3312 assert!(msg.contains("unknown"), "{msg}");
3313 }
3314
3315 #[test]
3318 fn include_of_an_inline_struct_literal_is_a_lower_error() {
3319 let file = parse_v1("module M = struct\ninclude struct val x = 1 end\nend");
3320 let err = lower_file_v1(&file).unwrap_err();
3321 let msg = err.to_string();
3322 assert!(msg.contains("struct"), "{msg}");
3323 assert!(msg.contains("name the module first"), "{msg}");
3324 }
3325
3326 #[test]
3329 fn include_of_a_coerced_module_is_a_lower_error() {
3330 let file = parse_v1(
3331 "module M = struct\n\
3332 module Base = struct val x = 1 end\n\
3333 include Base :> sig val x : int end\n\
3334 end",
3335 );
3336 let err = lower_file_v1(&file).unwrap_err();
3337 let msg = err.to_string();
3338 assert!(msg.contains("coerced module"), "{msg}");
3339 }
3340
3341 #[test]
3346 fn self_include_is_a_lower_error() {
3347 let file = parse_v1("module P = struct\ninclude P\nend");
3348 let err = lower_file_v1(&file).unwrap_err();
3349 assert!(err.to_string().contains("unknown module"), "{err}");
3350 }
3351
3352 #[test]
3357 fn cmd_tail_bridge_matches_flat_app_arg_shape() {
3358 let file = parse_v1(r"{\cmd{a}{b}}");
3359 let cst_v1::FileV1::Document { body, .. } = file else {
3360 panic!("expected a document file");
3361 };
3362 let ast_v1::Expr::Ops(chain) = body else {
3363 panic!("expected an operator-chain expression");
3364 };
3365 let ast_v1::Atomic::InlineText { elems, .. } = chain.head.head else {
3366 panic!("expected inline text");
3367 };
3368 let ast_v1::InlineElem::Cmd { tail, .. } = &elems[0] else {
3369 panic!("expected the first element to be a command");
3370 };
3371 let lowered = lower_cmd_tail(tail).unwrap();
3372 let cst::ast::CmdTail::Args { first, rest, .. } = lowered else {
3373 panic!("expected CmdTail::Args");
3374 };
3375 assert_eq!(
3376 rest.len(),
3377 1,
3378 "\\cmd{{a}}{{b}} has exactly one trailing arg"
3379 );
3380 assert!(matches!(
3381 &*first.0,
3382 cst::ast::AppArg::Atom {
3383 stage: None,
3384 atom: cst::ast::Atomic::InlineText { .. },
3385 ..
3386 }
3387 ));
3388 assert!(matches!(
3389 &*rest[0].0,
3390 cst::ast::AppArg::Atom {
3391 stage: None,
3392 atom: cst::ast::Atomic::InlineText { .. },
3393 ..
3394 }
3395 ));
3396 }
3397}