#[cfg(test)]
mod tests {
use crate::engine::grammar::SPG;
use crate::typing::{Context, Type, TypingSynth};
const TYPED_EXPR: &str = r#"
Identifier ::= /[a-z][a-zA-Z0-9]*/
Type ::= 'int' | 'bool'
Variable(var) ::= Identifier[x]
Let(let_) ::= 'let' Identifier[n] ':' Type[τ] '=' Expr[v] 'in' Expr[b]
Expr ::= Variable | Let
x ∈ Γ
----------- (var)
Γ(x)
Γ ⊢ v : τ, Γ[n:τ] ⊢ b : ?T
----------- (let_)
?T
"#;
const CONSTRAINED_EXPR: &str = r#"
Identifier ::= /[a-z][a-zA-Z0-9]*/
Type ::= 'int' | 'bool'
Variable(var) ::= Identifier[x]
Annotated(ann) ::= '(' Expr[e] ':' Type[τ] ')'
Expr ::= Variable | Annotated
x ∈ Γ
----------- (var)
Γ(x)
Γ ⊢ e : τ
----------- (ann)
τ
"#;
fn parse(grammar: &SPG, input: &str, ctx: &Context) -> Option<bool> {
let mut synth = TypingSynth::new(grammar.clone(), input);
match synth.parse_with(ctx) {
Ok(ast) => Some(ast.is_complete()),
Err(_) => None,
}
}
#[test]
fn monotonicity_typed_let_expressions() {
let grammar = SPG::load(TYPED_EXPR).unwrap();
let ctx = Context::new();
let inputs = [
"let x : int = 1 in x",
"let a : bool = true in a",
"let x : int = 1 in let y : int = x in y",
];
for input in &inputs {
if parse(&grammar, input, &ctx).is_none() {
continue;
}
for len in 0..=input.len() {
let prefix = &input[..len];
if prefix.trim().is_empty() {
continue;
}
assert!(
parse(&grammar, prefix, &ctx).is_some(),
"monotonicity violated: '{}' parses but prefix '{}' does not",
input,
prefix
);
}
}
}
#[test]
fn monotonicity_under_context_extension() {
let grammar = SPG::load(TYPED_EXPR).unwrap();
let base_ctx = Context::new();
let mut extended = Context::new();
extended.add("z".to_string(), Type::raw("int"));
let inputs = ["let x : int = 1 in x", "x", "let x : int = x in x"];
for input in &inputs {
if parse(&grammar, input, &base_ctx) == Some(true) {
assert_eq!(
parse(&grammar, input, &extended),
Some(true),
"context extension broke parseability of '{}'",
input
);
}
}
}
#[test]
fn evidence_narrows_under_prefix_extension() {
let grammar = SPG::load(CONSTRAINED_EXPR).unwrap();
let mut ctx = Context::new();
ctx.add("x".to_string(), Type::raw("int"));
for (prefix, full) in [("( x :", "( x : int )"), ("(", "( x : int )")] {
let mut synth_p = TypingSynth::new(grammar.clone(), prefix);
let mut synth_f = TypingSynth::new(grammar.clone(), full);
let ast_p = synth_p.parse_with(&ctx);
let ast_f = synth_f.parse_with(&ctx);
if let (Ok(p), Ok(f)) = (ast_p, ast_f) {
assert!(
!p.is_complete() || f.is_complete(),
"prefix '{}' is complete but full input '{}' is not — evidence non-monotone",
prefix,
full
);
}
}
}
#[test]
fn witness_exists_for_partial_annotation() {
let grammar = SPG::load(CONSTRAINED_EXPR).unwrap();
let mut ctx = Context::new();
ctx.add("x".to_string(), Type::raw("int"));
let mut synth = TypingSynth::new(grammar.clone(), "( x :");
let ast = synth.parse_with(&ctx).unwrap();
assert!(
!ast.is_complete(),
"annotation prefix should not be complete"
);
let closed = synth.feed_with("int )", &ctx).unwrap();
assert!(
closed.is_complete(),
"annotated expression should be complete after feeding closing tokens"
);
}
#[test]
fn witness_exists_for_partial_let() {
let grammar = SPG::load(TYPED_EXPR).unwrap();
let ctx = Context::new();
let prefixes = [
"let",
"let x",
"let x :",
"let x : int",
"let x : int =",
"let x : int = 1",
"let x : int = 1 in",
];
for prefix in &prefixes {
let mut synth = TypingSynth::new(grammar.clone(), *prefix);
if synth.parse_with(&ctx).is_err() {
continue;
}
let completions = ["x", " 1 in x"];
for completion in &completions {
let mut synth =
TypingSynth::new(grammar.clone(), format!("{}{}", prefix, completion));
if let Ok(ast) = synth.parse_with(&ctx)
&& ast.is_complete()
{
return;
}
}
}
}
}