iter ct.4.3: prose qualifier-trim-on-print + ordering_match snapshot
This commit is contained in:
+203
-87
@@ -54,9 +54,16 @@ fn write_module(out: &mut String, m: &Module) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ct.4.3: the file's own module name is the owner-qualifier that
|
||||
// the prose surface trims when it appears on intra-module
|
||||
// `Type::Con.name`. Cross-module qualifiers (any other owner)
|
||||
// round-trip verbatim. Threaded through every recursive write_*
|
||||
// that can reach a `Type::Con`.
|
||||
let owning_module = m.name.as_str();
|
||||
|
||||
for def in &m.defs {
|
||||
out.push('\n');
|
||||
write_def(out, def, 0);
|
||||
write_def(out, def, 0, owning_module);
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
@@ -72,13 +79,13 @@ fn write_import(out: &mut String, imp: &Import) {
|
||||
|
||||
// ---- defs -----------------------------------------------------------------
|
||||
|
||||
fn write_def(out: &mut String, def: &Def, level: usize) {
|
||||
fn write_def(out: &mut String, def: &Def, level: usize, owning_module: &str) {
|
||||
match def {
|
||||
Def::Type(td) => write_type_def(out, td, level),
|
||||
Def::Fn(fd) => write_fn_def(out, fd, level),
|
||||
Def::Const(cd) => write_const_def(out, cd, level),
|
||||
Def::Class(c) => write_class_def(out, c, level),
|
||||
Def::Instance(i) => write_instance_def(out, i, level),
|
||||
Def::Type(td) => write_type_def(out, td, level, owning_module),
|
||||
Def::Fn(fd) => write_fn_def(out, fd, level, owning_module),
|
||||
Def::Const(cd) => write_const_def(out, cd, level, owning_module),
|
||||
Def::Class(c) => write_class_def(out, c, level, owning_module),
|
||||
Def::Instance(i) => write_instance_def(out, i, level, owning_module),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +191,7 @@ fn wrap_words(piece: &str, budget: usize) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
fn write_type_def(out: &mut String, td: &TypeDef, level: usize) {
|
||||
fn write_type_def(out: &mut String, td: &TypeDef, level: usize, owning_module: &str) {
|
||||
write_doc(out, &td.doc, level);
|
||||
indent(out, level);
|
||||
out.push_str("data ");
|
||||
@@ -204,14 +211,14 @@ fn write_type_def(out: &mut String, td: &TypeDef, level: usize) {
|
||||
if i > 0 {
|
||||
out.push_str(" | ");
|
||||
}
|
||||
write_ctor(out, c);
|
||||
write_ctor(out, c, owning_module);
|
||||
}
|
||||
if td.drop_iterative {
|
||||
out.push_str(" with drop-iterative");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_ctor(out: &mut String, c: &Ctor) {
|
||||
fn write_ctor(out: &mut String, c: &Ctor, owning_module: &str) {
|
||||
out.push_str(&c.name);
|
||||
if !c.fields.is_empty() {
|
||||
out.push('(');
|
||||
@@ -219,13 +226,13 @@ fn write_ctor(out: &mut String, c: &Ctor) {
|
||||
if i > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
write_type(out, f);
|
||||
write_type(out, f, owning_module);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fn_def(out: &mut String, fd: &FnDef, level: usize) {
|
||||
fn write_fn_def(out: &mut String, fd: &FnDef, level: usize, owning_module: &str) {
|
||||
// Iter 19b: render `suppress` entries as `// @suppress <code>: <reason>`
|
||||
// comment lines **above** the doc string. They are contract metadata
|
||||
// that the LLM-reader needs to see to understand why an annotation
|
||||
@@ -263,7 +270,7 @@ fn write_fn_def(out: &mut String, fd: &FnDef, level: usize) {
|
||||
}
|
||||
out.push_str(&c.class);
|
||||
out.push(' ');
|
||||
write_type(out, &c.type_);
|
||||
write_type(out, &c.type_, owning_module);
|
||||
}
|
||||
}
|
||||
out.push('\n');
|
||||
@@ -293,10 +300,10 @@ fn write_fn_def(out: &mut String, fd: &FnDef, level: usize) {
|
||||
.get(i)
|
||||
.copied()
|
||||
.unwrap_or(ParamMode::Implicit);
|
||||
write_mode_type(out, ty, mode);
|
||||
write_mode_type(out, ty, mode, owning_module);
|
||||
}
|
||||
out.push_str(") -> ");
|
||||
write_mode_type(out, ret, *ret_mode);
|
||||
write_mode_type(out, ret, *ret_mode, owning_module);
|
||||
if !effects.is_empty() {
|
||||
out.push_str(" with ");
|
||||
// Stable order: as written in the AST. Effects are
|
||||
@@ -314,30 +321,30 @@ fn write_fn_def(out: &mut String, fd: &FnDef, level: usize) {
|
||||
// nothing crashes; this path is unreachable for typechecked
|
||||
// input.
|
||||
out.push_str(") -> ");
|
||||
write_type(out, fn_ty);
|
||||
write_type(out, fn_ty, owning_module);
|
||||
}
|
||||
out.push_str(" {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, &fd.body, level + 1);
|
||||
write_term(out, &fd.body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
}
|
||||
|
||||
fn write_const_def(out: &mut String, cd: &ConstDef, level: usize) {
|
||||
fn write_const_def(out: &mut String, cd: &ConstDef, level: usize, owning_module: &str) {
|
||||
write_doc(out, &cd.doc, level);
|
||||
indent(out, level);
|
||||
out.push_str("const ");
|
||||
out.push_str(&cd.name);
|
||||
out.push_str(": ");
|
||||
write_type(out, &cd.ty);
|
||||
write_type(out, &cd.ty, owning_module);
|
||||
out.push_str(" = ");
|
||||
write_term(out, &cd.value, level);
|
||||
write_term(out, &cd.value, level, owning_module);
|
||||
}
|
||||
|
||||
/// Render a class declaration in Rust-flavoured Form-B:
|
||||
/// `class Name a [extends Super] { <methods> }`.
|
||||
fn write_class_def(out: &mut String, c: &ClassDef, level: usize) {
|
||||
fn write_class_def(out: &mut String, c: &ClassDef, level: usize, owning_module: &str) {
|
||||
write_doc(out, &c.doc, level);
|
||||
indent(out, level);
|
||||
out.push_str("class ");
|
||||
@@ -350,7 +357,7 @@ fn write_class_def(out: &mut String, c: &ClassDef, level: usize) {
|
||||
}
|
||||
out.push_str(" {\n");
|
||||
for m in &c.methods {
|
||||
write_class_method(out, m, level + 1);
|
||||
write_class_method(out, m, level + 1, owning_module);
|
||||
}
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
@@ -359,7 +366,7 @@ fn write_class_def(out: &mut String, c: &ClassDef, level: usize) {
|
||||
/// Render one method line of a class: signature plus optional
|
||||
/// `default { <body> }`. Class methods carry no parameter names
|
||||
/// in the AST, so slots are named `x`, `x1`, ….
|
||||
fn write_class_method(out: &mut String, m: &ClassMethod, level: usize) {
|
||||
fn write_class_method(out: &mut String, m: &ClassMethod, level: usize, owning_module: &str) {
|
||||
indent(out, level);
|
||||
out.push_str("fn ");
|
||||
out.push_str(&m.name);
|
||||
@@ -379,10 +386,10 @@ fn write_class_method(out: &mut String, m: &ClassMethod, level: usize) {
|
||||
}
|
||||
out.push_str(": ");
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit);
|
||||
write_mode_type(out, p, mode);
|
||||
write_mode_type(out, p, mode, owning_module);
|
||||
}
|
||||
out.push_str(") -> ");
|
||||
write_mode_type(out, ret, *ret_mode);
|
||||
write_mode_type(out, ret, *ret_mode, owning_module);
|
||||
if !effects.is_empty() {
|
||||
out.push_str(" with ");
|
||||
for (i, e) in effects.iter().enumerate() {
|
||||
@@ -397,12 +404,12 @@ fn write_class_method(out: &mut String, m: &ClassMethod, level: usize) {
|
||||
// bare type so nothing crashes; this path is unreachable for
|
||||
// typechecked input.
|
||||
out.push_str(") -> ");
|
||||
write_type(out, &m.ty);
|
||||
write_type(out, &m.ty, owning_module);
|
||||
}
|
||||
if let Some(body) = &m.default {
|
||||
out.push_str(" default {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, body, level + 1);
|
||||
write_term(out, body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push_str("}\n");
|
||||
@@ -413,16 +420,16 @@ fn write_class_method(out: &mut String, m: &ClassMethod, level: usize) {
|
||||
|
||||
/// Render an instance declaration in Form-B:
|
||||
/// `instance Class Type { <method bodies> }`.
|
||||
fn write_instance_def(out: &mut String, i: &InstanceDef, level: usize) {
|
||||
fn write_instance_def(out: &mut String, i: &InstanceDef, level: usize, owning_module: &str) {
|
||||
write_doc(out, &i.doc, level);
|
||||
indent(out, level);
|
||||
out.push_str("instance ");
|
||||
out.push_str(&i.class);
|
||||
out.push(' ');
|
||||
write_type(out, &i.type_);
|
||||
write_type(out, &i.type_, owning_module);
|
||||
out.push_str(" {\n");
|
||||
for m in &i.methods {
|
||||
write_instance_method(out, m, level + 1);
|
||||
write_instance_method(out, m, level + 1, owning_module);
|
||||
}
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
@@ -431,13 +438,13 @@ fn write_instance_def(out: &mut String, i: &InstanceDef, level: usize) {
|
||||
/// Render one method body of an instance: `fn name { <body> }`.
|
||||
/// The signature is recoverable from the class declaration via
|
||||
/// `InstanceDef.class` lookup, so it is intentionally elided.
|
||||
fn write_instance_method(out: &mut String, m: &InstanceMethod, level: usize) {
|
||||
fn write_instance_method(out: &mut String, m: &InstanceMethod, level: usize, owning_module: &str) {
|
||||
indent(out, level);
|
||||
out.push_str("fn ");
|
||||
out.push_str(&m.name);
|
||||
out.push_str(" {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, &m.body, level + 1);
|
||||
write_term(out, &m.body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push_str("}\n");
|
||||
@@ -445,32 +452,42 @@ fn write_instance_method(out: &mut String, m: &InstanceMethod, level: usize) {
|
||||
|
||||
// ---- types ----------------------------------------------------------------
|
||||
|
||||
fn write_mode_type(out: &mut String, t: &Type, mode: ParamMode) {
|
||||
fn write_mode_type(out: &mut String, t: &Type, mode: ParamMode, owning_module: &str) {
|
||||
match mode {
|
||||
ParamMode::Implicit => write_type(out, t),
|
||||
ParamMode::Implicit => write_type(out, t, owning_module),
|
||||
ParamMode::Own => {
|
||||
out.push_str("own ");
|
||||
write_type(out, t);
|
||||
write_type(out, t, owning_module);
|
||||
}
|
||||
ParamMode::Borrow => {
|
||||
out.push_str("borrow ");
|
||||
write_type(out, t);
|
||||
write_type(out, t, owning_module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_type(out: &mut String, t: &Type) {
|
||||
fn write_type(out: &mut String, t: &Type, owning_module: &str) {
|
||||
match t {
|
||||
Type::Var { name } => out.push_str(name),
|
||||
Type::Con { name, args } => {
|
||||
out.push_str(name);
|
||||
// Ct.4.3: trim a qualifier whose owner matches the current
|
||||
// file's module: `prelude.Ordering` printed from inside
|
||||
// `prelude` becomes bare `Ordering`. Cross-module qualifiers
|
||||
// (any other owner) survive verbatim. Mirrors the post-ct.1
|
||||
// canonical-form rule "bare = local, qualified = cross-
|
||||
// module" on the prose surface.
|
||||
let display_name = match name.split_once('.') {
|
||||
Some((owner, suffix)) if owner == owning_module => suffix,
|
||||
_ => name.as_str(),
|
||||
};
|
||||
out.push_str(display_name);
|
||||
if !args.is_empty() {
|
||||
out.push('<');
|
||||
for (i, a) in args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
write_type(out, a);
|
||||
write_type(out, a, owning_module);
|
||||
}
|
||||
out.push('>');
|
||||
}
|
||||
@@ -493,10 +510,10 @@ fn write_type(out: &mut String, t: &Type) {
|
||||
.unwrap_or(ParamMode::Implicit);
|
||||
// In a bare `Type::Fn` outside a fn signature we have no
|
||||
// parameter names, so render `mode T` only (no `name:`).
|
||||
write_mode_type(out, p, mode);
|
||||
write_mode_type(out, p, mode, owning_module);
|
||||
}
|
||||
out.push_str(") -> ");
|
||||
write_mode_type(out, ret, *ret_mode);
|
||||
write_mode_type(out, ret, *ret_mode, owning_module);
|
||||
if !effects.is_empty() {
|
||||
out.push_str(" with ");
|
||||
for (i, e) in effects.iter().enumerate() {
|
||||
@@ -516,7 +533,7 @@ fn write_type(out: &mut String, t: &Type) {
|
||||
out.push_str(v);
|
||||
}
|
||||
out.push_str("> ");
|
||||
write_type(out, body);
|
||||
write_type(out, body, owning_module);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,11 +605,11 @@ fn as_unary_not(t: &Term) -> Option<&Term> {
|
||||
/// is `PREC_NONE`, so a binary op at the root never wraps itself. All
|
||||
/// internal recursion goes through this same fn with the appropriate
|
||||
/// `parent_prec` for the sub-position.
|
||||
fn write_term(out: &mut String, t: &Term, level: usize) {
|
||||
write_term_prec(out, t, level, PREC_NONE);
|
||||
fn write_term(out: &mut String, t: &Term, level: usize, owning_module: &str) {
|
||||
write_term_prec(out, t, level, PREC_NONE, owning_module);
|
||||
}
|
||||
|
||||
fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8, owning_module: &str) {
|
||||
// Polish 1+2: infix binary operators with paren elision.
|
||||
if let Some((op, lhs, rhs, prec, left_assoc)) = as_binop(t) {
|
||||
let need_parens = prec < parent_prec;
|
||||
@@ -611,11 +628,11 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
// parens to avoid implying we accept it.
|
||||
(prec + 1, prec + 1)
|
||||
};
|
||||
write_term_prec(out, lhs, level, lhs_floor);
|
||||
write_term_prec(out, lhs, level, lhs_floor, owning_module);
|
||||
out.push(' ');
|
||||
out.push_str(op);
|
||||
out.push(' ');
|
||||
write_term_prec(out, rhs, level, rhs_floor);
|
||||
write_term_prec(out, rhs, level, rhs_floor, owning_module);
|
||||
if need_parens {
|
||||
out.push(')');
|
||||
}
|
||||
@@ -626,7 +643,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
// so anything below atomic (i.e. another binary op) wraps.
|
||||
if let Some(arg) = as_unary_not(t) {
|
||||
out.push('!');
|
||||
write_term_prec(out, arg, level, PREC_ATOMIC);
|
||||
write_term_prec(out, arg, level, PREC_ATOMIC, owning_module);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,7 +659,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
}
|
||||
// Callee is rendered at PREC_ATOMIC: a binary op as callee
|
||||
// (rare, but legal) would need parens.
|
||||
write_term_prec(out, callee, level, PREC_ATOMIC);
|
||||
write_term_prec(out, callee, level, PREC_ATOMIC, owning_module);
|
||||
out.push('(');
|
||||
for (i, a) in args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
@@ -650,7 +667,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
}
|
||||
// Args sit in their own paren context — no outer prec
|
||||
// floor. Pass NONE so binary ops inside don't wrap.
|
||||
write_term_prec(out, a, level, PREC_NONE);
|
||||
write_term_prec(out, a, level, PREC_NONE, owning_module);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
@@ -675,20 +692,20 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
&& count_free_var(name, body) == 1
|
||||
&& {
|
||||
let mut buf = String::new();
|
||||
write_term(&mut buf, value, level);
|
||||
write_term(&mut buf, value, level, owning_module);
|
||||
!buf.contains('\n') && buf.len() <= RHS_INLINE_BUDGET
|
||||
};
|
||||
if inlinable {
|
||||
let inlined = subst_var_with_term(body, name, value);
|
||||
write_term_prec(out, &inlined, level, parent_prec);
|
||||
write_term_prec(out, &inlined, level, parent_prec, owning_module);
|
||||
} else {
|
||||
out.push_str("let ");
|
||||
out.push_str(name);
|
||||
out.push_str(" = ");
|
||||
write_term(out, value, level);
|
||||
write_term(out, value, level, owning_module);
|
||||
out.push_str(";\n");
|
||||
indent(out, level);
|
||||
write_term(out, body, level);
|
||||
write_term(out, body, level, owning_module);
|
||||
}
|
||||
}
|
||||
Term::LetRec {
|
||||
@@ -722,10 +739,10 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
.get(i)
|
||||
.copied()
|
||||
.unwrap_or(ParamMode::Implicit);
|
||||
write_mode_type(out, pty, mode);
|
||||
write_mode_type(out, pty, mode, owning_module);
|
||||
}
|
||||
out.push_str(") -> ");
|
||||
write_mode_type(out, ret, *ret_mode);
|
||||
write_mode_type(out, ret, *ret_mode, owning_module);
|
||||
if !effects.is_empty() {
|
||||
out.push_str(" with ");
|
||||
for (i, e) in effects.iter().enumerate() {
|
||||
@@ -737,28 +754,28 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
}
|
||||
} else {
|
||||
out.push_str(") -> ");
|
||||
write_type(out, ty);
|
||||
write_type(out, ty, owning_module);
|
||||
}
|
||||
out.push_str(" {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, body, level + 1);
|
||||
write_term(out, body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push_str("};\n");
|
||||
indent(out, level);
|
||||
write_term(out, in_term, level);
|
||||
write_term(out, in_term, level, owning_module);
|
||||
}
|
||||
Term::If { cond, then, else_ } => {
|
||||
out.push_str("if ");
|
||||
write_term(out, cond, level);
|
||||
write_term(out, cond, level, owning_module);
|
||||
out.push_str(" {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, then, level + 1);
|
||||
write_term(out, then, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push_str("} else {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, else_, level + 1);
|
||||
write_term(out, else_, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
@@ -774,7 +791,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
if i > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
write_term(out, a, level);
|
||||
write_term(out, a, level, owning_module);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
@@ -783,6 +800,12 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
ctor,
|
||||
args,
|
||||
} => {
|
||||
// Ct.4.3: the Ctor `type_name` field is intentionally
|
||||
// elided from the prose surface — the type is recoverable
|
||||
// from typecheck context, and showing it on every Ctor
|
||||
// would clutter the read. The qualifier-trim that applies
|
||||
// to `Type::Con.name` therefore has no analogue here:
|
||||
// there is no `out.push_str(type_name)` site to trim.
|
||||
out.push_str(ctor);
|
||||
if !args.is_empty() {
|
||||
out.push('(');
|
||||
@@ -790,20 +813,20 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
if i > 0 {
|
||||
out.push_str(", ");
|
||||
}
|
||||
write_term(out, a, level);
|
||||
write_term(out, a, level, owning_module);
|
||||
}
|
||||
out.push(')');
|
||||
}
|
||||
}
|
||||
Term::Match { scrutinee, arms } => {
|
||||
out.push_str("match ");
|
||||
write_term(out, scrutinee, level);
|
||||
write_term(out, scrutinee, level, owning_module);
|
||||
out.push_str(" {\n");
|
||||
for (i, arm) in arms.iter().enumerate() {
|
||||
indent(out, level + 1);
|
||||
write_pattern(out, &arm.pat);
|
||||
out.push_str(" => ");
|
||||
write_term(out, &arm.body, level + 1);
|
||||
write_term(out, &arm.body, level + 1, owning_module);
|
||||
if i + 1 < arms.len() {
|
||||
out.push(',');
|
||||
}
|
||||
@@ -827,10 +850,10 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
}
|
||||
out.push_str(pname);
|
||||
out.push_str(": ");
|
||||
write_type(out, pty);
|
||||
write_type(out, pty, owning_module);
|
||||
}
|
||||
out.push_str("| -> ");
|
||||
write_type(out, ret_ty);
|
||||
write_type(out, ret_ty, owning_module);
|
||||
if !effects.is_empty() {
|
||||
out.push_str(" with ");
|
||||
for (i, e) in effects.iter().enumerate() {
|
||||
@@ -842,7 +865,7 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
}
|
||||
out.push_str(" {\n");
|
||||
indent(out, level + 1);
|
||||
write_term(out, body, level + 1);
|
||||
write_term(out, body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
@@ -850,14 +873,14 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
Term::Seq { lhs, rhs } => {
|
||||
// Semicolon-as-discard. `lhs;` on its own line, `rhs` on
|
||||
// the next at the same indent. The caller already indented us.
|
||||
write_term(out, lhs, level);
|
||||
write_term(out, lhs, level, owning_module);
|
||||
out.push_str(";\n");
|
||||
indent(out, level);
|
||||
write_term(out, rhs, level);
|
||||
write_term(out, rhs, level, owning_module);
|
||||
}
|
||||
Term::Clone { value } => {
|
||||
out.push_str("clone(");
|
||||
write_term(out, value, level);
|
||||
write_term(out, value, level, owning_module);
|
||||
out.push(')');
|
||||
}
|
||||
Term::ReuseAs { source, body } => {
|
||||
@@ -869,10 +892,10 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8) {
|
||||
// single-expression bodies (the 99% Ctor case) stay inline,
|
||||
// collapsing the whole match arm to one line.
|
||||
out.push_str("reuse ");
|
||||
write_term(out, source, level);
|
||||
write_term(out, source, level, owning_module);
|
||||
out.push_str(" as ");
|
||||
let mut body_buf = String::new();
|
||||
write_term(&mut body_buf, body, level + 1);
|
||||
write_term(&mut body_buf, body, level + 1, owning_module);
|
||||
if body_buf.contains('\n') {
|
||||
out.push_str("{\n");
|
||||
indent(out, level + 1);
|
||||
@@ -1178,13 +1201,13 @@ mod tests {
|
||||
|
||||
fn render_term(t: &Term) -> String {
|
||||
let mut out = String::new();
|
||||
write_term(&mut out, t, 0);
|
||||
write_term(&mut out, t, 0, "");
|
||||
out
|
||||
}
|
||||
|
||||
fn render_type(t: &Type) -> String {
|
||||
let mut out = String::new();
|
||||
write_type(&mut out, t);
|
||||
write_type(&mut out, t, "");
|
||||
out
|
||||
}
|
||||
|
||||
@@ -1647,7 +1670,7 @@ mod tests {
|
||||
doc: None,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_fn_def(&mut out, &fd, 0);
|
||||
write_fn_def(&mut out, &fd, 0, "");
|
||||
assert!(out.contains("xs: own IntList"), "got:\n{out}");
|
||||
assert!(out.contains(") -> Int "), "got:\n{out}");
|
||||
}
|
||||
@@ -1663,7 +1686,7 @@ mod tests {
|
||||
doc: None,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_fn_def(&mut out, &fd, 0);
|
||||
write_fn_def(&mut out, &fd, 0, "");
|
||||
assert!(out.contains("() -> Unit with IO {"), "got:\n{out}");
|
||||
}
|
||||
|
||||
@@ -1695,7 +1718,7 @@ mod tests {
|
||||
doc: Some("Take ownership.".into()),
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_fn_def(&mut out, &fd, 0);
|
||||
write_fn_def(&mut out, &fd, 0, "");
|
||||
// Suppress line is the FIRST line, before the doc.
|
||||
let first_line = out.lines().next().unwrap();
|
||||
assert_eq!(
|
||||
@@ -1735,7 +1758,7 @@ mod tests {
|
||||
doc: None,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_fn_def(&mut out, &fd, 0);
|
||||
write_fn_def(&mut out, &fd, 0, "");
|
||||
let lines: Vec<&str> = out.lines().collect();
|
||||
assert_eq!(lines[0], "// @suppress over-strict-mode: first reason");
|
||||
assert_eq!(lines[1], "// @suppress some-other-code: second reason");
|
||||
@@ -1757,7 +1780,7 @@ mod tests {
|
||||
doc: Some("just a doc".into()),
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_fn_def(&mut out, &fd, 0);
|
||||
write_fn_def(&mut out, &fd, 0, "");
|
||||
assert!(
|
||||
!out.contains("// @suppress"),
|
||||
"no @suppress line for empty Vec; got:\n{out}"
|
||||
@@ -1782,7 +1805,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
assert!(out.starts_with("/// line one\n/// line two\n"), "got:\n{out}");
|
||||
}
|
||||
|
||||
@@ -1804,7 +1827,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
assert_eq!(out, "data IntList = Nil | Cons(Int, IntList)");
|
||||
}
|
||||
|
||||
@@ -1995,7 +2018,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
let doc_lines: Vec<&str> = out.lines().take_while(|l| l.starts_with("///")).collect();
|
||||
assert!(doc_lines.len() >= 2, "expected wrap, got:\n{out}");
|
||||
for line in &doc_lines {
|
||||
@@ -2025,7 +2048,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
assert!(out.starts_with("/// Short and snappy.\n"), "got:\n{out}");
|
||||
let doc_count = out.lines().filter(|l| l.starts_with("///")).count();
|
||||
assert_eq!(doc_count, 1);
|
||||
@@ -2047,7 +2070,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
let doc_lines: Vec<&str> = out.lines().take_while(|l| l.starts_with("///")).collect();
|
||||
// First line = unwrapped first paragraph; the rest = wrapped
|
||||
// second paragraph (≥ 2 lines).
|
||||
@@ -2072,7 +2095,7 @@ mod tests {
|
||||
drop_iterative: false,
|
||||
};
|
||||
let mut out = String::new();
|
||||
write_type_def(&mut out, &td, 0);
|
||||
write_type_def(&mut out, &td, 0, "");
|
||||
let doc_lines: Vec<&str> = out.lines().take_while(|l| l.starts_with("///")).collect();
|
||||
// The last doc line MUST have ≥ 2 words.
|
||||
let last = doc_lines.last().unwrap();
|
||||
@@ -2150,6 +2173,99 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// When a `Type::Con.name` is qualified with the owning
|
||||
/// module's own name (e.g. `prelude.Ordering` inside
|
||||
/// `prelude`'s own file), the prose printer must trim the
|
||||
/// qualifier and emit bare `Ordering`. Cross-module qualified
|
||||
/// refs (`prelude.Ordering` inside any non-prelude file)
|
||||
/// round-trip verbatim.
|
||||
#[test]
|
||||
fn type_con_qualifier_trimmed_when_owner_matches_module() {
|
||||
use ailang_core::ast::{Module, Def, FnDef, Type, Term, ParamMode};
|
||||
|
||||
let m = Module {
|
||||
schema: "ailang/v0".into(),
|
||||
name: "prelude".into(),
|
||||
imports: vec![],
|
||||
defs: vec![Def::Fn(FnDef {
|
||||
name: "noop".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::Con {
|
||||
name: "prelude.Ordering".into(),
|
||||
args: vec![],
|
||||
}),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Ctor {
|
||||
type_name: "prelude.Ordering".into(),
|
||||
ctor: "EQ".into(),
|
||||
args: vec![],
|
||||
},
|
||||
doc: None,
|
||||
suppress: vec![],
|
||||
})],
|
||||
};
|
||||
|
||||
let prose = module_to_prose(&m);
|
||||
assert!(
|
||||
prose.contains("-> Ordering"),
|
||||
"expected bare `Ordering` in fn return type (owner == file's \
|
||||
module); got prose:\n{}",
|
||||
prose
|
||||
);
|
||||
assert!(
|
||||
!prose.contains("prelude.Ordering"),
|
||||
"expected NO `prelude.Ordering` after trim; got prose:\n{}",
|
||||
prose
|
||||
);
|
||||
}
|
||||
|
||||
/// A cross-module Type::Con (owner != current file's module)
|
||||
/// must round-trip verbatim — no trim.
|
||||
#[test]
|
||||
fn type_con_qualifier_preserved_when_owner_differs() {
|
||||
use ailang_core::ast::{Module, Def, FnDef, Type, Term, ParamMode, Import};
|
||||
|
||||
let m = Module {
|
||||
schema: "ailang/v0".into(),
|
||||
name: "user".into(),
|
||||
imports: vec![Import { module: "prelude".into(), alias: None }],
|
||||
defs: vec![Def::Fn(FnDef {
|
||||
name: "lt".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::Con {
|
||||
name: "prelude.Ordering".into(),
|
||||
args: vec![],
|
||||
}),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Ctor {
|
||||
type_name: "prelude.Ordering".into(),
|
||||
ctor: "LT".into(),
|
||||
args: vec![],
|
||||
},
|
||||
doc: None,
|
||||
suppress: vec![],
|
||||
})],
|
||||
};
|
||||
|
||||
let prose = module_to_prose(&m);
|
||||
assert!(
|
||||
prose.contains("prelude.Ordering"),
|
||||
"expected qualified `prelude.Ordering` preserved (owner != file's \
|
||||
module); got prose:\n{}",
|
||||
prose
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested_match_keeps_each_level_at_its_own_indent() {
|
||||
// Three-deep nesting: each level lands one indent step deeper.
|
||||
|
||||
Reference in New Issue
Block a user