use bock_air::{interpret_context, lower_module, resolve_names_with_registry, NodeIdGen};
use bock_ast::Module;
use bock_errors::{FileId, Severity};
use bock_lexer::Lexer;
use bock_parser::Parser;
use bock_source::SourceFile;
use bock_types::TypeChecker;
use std::path::PathBuf;
fn parse(src: &str) -> Module {
let source = SourceFile::new(
FileId(1),
PathBuf::from("methodinfer_test.bock"),
src.to_string(),
);
let mut lexer = Lexer::new(&source);
let tokens = lexer.tokenize();
assert!(
lexer
.diagnostics()
.iter()
.all(|d| d.severity != Severity::Error),
"lexer errors in fixture:\n{}\nsrc:\n{src}",
lexer
.diagnostics()
.iter()
.map(|d| d.message.clone())
.collect::<Vec<_>>()
.join("\n"),
);
let mut parser = Parser::new(tokens, &source);
let module = parser.parse_module();
assert!(
parser
.diagnostics()
.iter()
.all(|d| d.severity != Severity::Error),
"parser errors in fixture:\n{}\nsrc:\n{src}",
parser
.diagnostics()
.iter()
.map(|d| d.message.clone())
.collect::<Vec<_>>()
.join("\n"),
);
module
}
fn check(src: &str) -> (usize, String) {
let module = parse(src);
let mut symbols = bock_air::SymbolTable::new();
let registry = bock_air::registry::ModuleRegistry::new();
let resolve_diags = resolve_names_with_registry(&module, &mut symbols, ®istry);
assert!(
!resolve_diags.has_errors(),
"resolve errors:\n{src}\n{:?}",
resolve_diags
.iter()
.map(|d| d.message.clone())
.collect::<Vec<_>>(),
);
let id_gen = NodeIdGen::new();
let mut air = lower_module(&module, &id_gen, &symbols);
let _ = interpret_context(&mut air);
let mut checker = TypeChecker::new();
checker.check_module(&mut air);
let msgs = checker
.diags
.iter()
.map(|d| format!("{} {}", d.code, d.message))
.collect::<Vec<_>>()
.join("\n");
(checker.diags.error_count(), msgs)
}
#[test]
fn box_map_own_type_param_inferred_at_call() {
let src = r#"module m
public record Box[T] {
value: T
}
impl Box {
public fn map[U](self, f: Fn(T) -> U) -> Box[U] {
Box { value: f(self.value) }
}
}
fn dbl(x: Int) -> Int { x * 2 }
fn needs_int(n: Int) -> Void {}
fn use_it() -> Void {
let b = Box { value: 21 }
let mapped = b.map(dbl)
needs_int(mapped.value)
}
"#;
let (errors, msgs) = check(src);
assert_eq!(
errors, 0,
"calling a generic method with its own type param should check clean, got:\n{msgs}"
);
}
#[test]
fn box_map_infers_distinct_result_type() {
let src = r#"module m
public record Box[T] {
value: T
}
impl Box {
public fn map[U](self, f: Fn(T) -> U) -> Box[U] {
Box { value: f(self.value) }
}
}
fn to_str(x: Int) -> String { "v" }
fn needs_string(s: String) -> Void {}
fn use_it() -> Void {
let b = Box { value: 21 }
let mapped = b.map(to_str)
needs_string(mapped.value)
}
"#;
let (errors, msgs) = check(src);
assert_eq!(
errors, 0,
"the method's own type param should infer a result type distinct from \
the receiver's, got:\n{msgs}"
);
}
#[test]
fn box_map_wrong_arg_type_still_errors() {
let src = r#"module m
public record Box[T] {
value: T
}
impl Box {
public fn map[U](self, f: Fn(T) -> U) -> Box[U] {
Box { value: f(self.value) }
}
}
fn bad(x: String) -> Int { 0 }
fn use_it() -> Void {
let b = Box { value: 21 }
let mapped = b.map(bad)
let _ = mapped.value
}
"#;
let (errors, _msgs) = check(src);
assert!(
errors > 0,
"passing a closure whose param type conflicts with the receiver's \
pinned type param must be rejected"
);
}