Universally quantified type variables
Implement universally quantified type variables, both explicitly given by the user and inferred by the type inference algorithm.
This commit is contained in:
parent
7960c3270e
commit
ecb4c0f803
17 changed files with 634 additions and 111 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -5,7 +5,9 @@ name = "achilles"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bimap",
|
||||||
"clap",
|
"clap",
|
||||||
|
"crate-root",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"inkwell",
|
"inkwell",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -57,6 +59,12 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bimap"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f92b72b8f03128773278bf74418b9205f3d2a12c39a61f92395f47af390c32bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -140,6 +148,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crate-root"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59c6fe4622b269032d2c5140a592d67a9c409031d286174fcde172fbed86f0d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.11"
|
version = "0.99.11"
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
bimap = "0.6.0"
|
||||||
clap = "3.0.0-beta.2"
|
clap = "3.0.0-beta.2"
|
||||||
derive_more = "0.99.11"
|
derive_more = "0.99.11"
|
||||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm11-0"] }
|
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm11-0"] }
|
||||||
|
@ -18,3 +19,6 @@ pratt = "0.3.0"
|
||||||
proptest = "1.0.0"
|
proptest = "1.0.0"
|
||||||
test-strategy = "0.1.1"
|
test-strategy = "0.1.1"
|
||||||
thiserror = "1.0.24"
|
thiserror = "1.0.24"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
crate-root = "0.1.3"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
fn id x = x
|
fn id x = x
|
||||||
fn plus x y = x + y
|
fn plus (x: int) (y: int) = x + y
|
||||||
fn main = plus (id 2) 7
|
fn main = plus (id 2) 7
|
||||||
|
|
|
@ -222,6 +222,12 @@ pub enum Decl<'a, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Decl<'a, T> {
|
impl<'a, T> Decl<'a, T> {
|
||||||
|
pub fn type_(&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Decl::Fun { type_, .. } => type_,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn traverse_type<F, U, E>(self, f: F) -> Result<Decl<'a, U>, E>
|
pub fn traverse_type<F, U, E>(self, f: F) -> Result<Decl<'a, U>, E>
|
||||||
where
|
where
|
||||||
F: Fn(T) -> Result<U, E> + Clone,
|
F: Fn(T) -> Result<U, E> + Clone,
|
||||||
|
|
162
src/ast/mod.rs
162
src/ast/mod.rs
|
@ -1,6 +1,7 @@
|
||||||
pub(crate) mod hir;
|
pub(crate) mod hir;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ impl<'a> Literal<'a> {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Binding<'a> {
|
pub struct Binding<'a> {
|
||||||
pub ident: Ident<'a>,
|
pub ident: Ident<'a>,
|
||||||
pub type_: Option<Type>,
|
pub type_: Option<Type<'a>>,
|
||||||
pub body: Expr<'a>,
|
pub body: Expr<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ impl<'a> Binding<'a> {
|
||||||
fn to_owned(&self) -> Binding<'static> {
|
fn to_owned(&self) -> Binding<'static> {
|
||||||
Binding {
|
Binding {
|
||||||
ident: self.ident.to_owned(),
|
ident: self.ident.to_owned(),
|
||||||
type_: self.type_.clone(),
|
type_: self.type_.as_ref().map(|t| t.to_owned()),
|
||||||
body: self.body.to_owned(),
|
body: self.body.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +178,7 @@ pub enum Expr<'a> {
|
||||||
|
|
||||||
Ascription {
|
Ascription {
|
||||||
expr: Box<Expr<'a>>,
|
expr: Box<Expr<'a>>,
|
||||||
type_: Type,
|
type_: Type<'a>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,20 +216,46 @@ impl<'a> Expr<'a> {
|
||||||
},
|
},
|
||||||
Expr::Ascription { expr, type_ } => Expr::Ascription {
|
Expr::Ascription { expr, type_ } => Expr::Ascription {
|
||||||
expr: Box::new((**expr).to_owned()),
|
expr: Box::new((**expr).to_owned()),
|
||||||
type_: type_.clone(),
|
type_: type_.to_owned(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Arg<'a> {
|
||||||
|
pub ident: Ident<'a>,
|
||||||
|
pub type_: Option<Type<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Arg<'a> {
|
||||||
|
pub fn to_owned(&self) -> Arg<'static> {
|
||||||
|
Arg {
|
||||||
|
ident: self.ident.to_owned(),
|
||||||
|
type_: self.type_.as_ref().map(Type::to_owned),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Arg<'a> {
|
||||||
|
type Error = <Ident<'a> as TryFrom<&'a str>>::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Arg {
|
||||||
|
ident: Ident::try_from(value)?,
|
||||||
|
type_: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Fun<'a> {
|
pub struct Fun<'a> {
|
||||||
pub args: Vec<Ident<'a>>,
|
pub args: Vec<Arg<'a>>,
|
||||||
pub body: Expr<'a>,
|
pub body: Expr<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Fun<'a> {
|
impl<'a> Fun<'a> {
|
||||||
fn to_owned(&self) -> Fun<'static> {
|
pub fn to_owned(&self) -> Fun<'static> {
|
||||||
Fun {
|
Fun {
|
||||||
args: self.args.iter().map(|arg| arg.to_owned()).collect(),
|
args: self.args.iter().map(|arg| arg.to_owned()).collect(),
|
||||||
body: self.body.to_owned(),
|
body: self.body.to_owned(),
|
||||||
|
@ -236,40 +263,147 @@ impl<'a> Fun<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Decl<'a> {
|
pub enum Decl<'a> {
|
||||||
Fun { name: Ident<'a>, body: Fun<'a> },
|
Fun { name: Ident<'a>, body: Fun<'a> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct FunctionType {
|
pub struct FunctionType<'a> {
|
||||||
pub args: Vec<Type>,
|
pub args: Vec<Type<'a>>,
|
||||||
pub ret: Box<Type>,
|
pub ret: Box<Type<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FunctionType {
|
impl<'a> FunctionType<'a> {
|
||||||
|
pub fn to_owned(&self) -> FunctionType<'static> {
|
||||||
|
FunctionType {
|
||||||
|
args: self.args.iter().map(|a| a.to_owned()).collect(),
|
||||||
|
ret: Box::new((*self.ret).to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for FunctionType<'a> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "fn {} -> {}", self.args.iter().join(", "), self.ret)
|
write!(f, "fn {} -> {}", self.args.iter().join(", "), self.ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Type {
|
pub enum Type<'a> {
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
Bool,
|
Bool,
|
||||||
CString,
|
CString,
|
||||||
Function(FunctionType),
|
Var(Ident<'a>),
|
||||||
|
Function(FunctionType<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Type {
|
impl<'a> Type<'a> {
|
||||||
|
pub fn to_owned(&self) -> Type<'static> {
|
||||||
|
match self {
|
||||||
|
Type::Int => Type::Int,
|
||||||
|
Type::Float => Type::Float,
|
||||||
|
Type::Bool => Type::Bool,
|
||||||
|
Type::CString => Type::CString,
|
||||||
|
Type::Var(v) => Type::Var(v.to_owned()),
|
||||||
|
Type::Function(f) => Type::Function(f.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alpha_equiv(&self, other: &Self) -> bool {
|
||||||
|
fn do_alpha_equiv<'a>(
|
||||||
|
substs: &mut HashMap<&'a Ident<'a>, &'a Ident<'a>>,
|
||||||
|
lhs: &'a Type,
|
||||||
|
rhs: &'a Type,
|
||||||
|
) -> bool {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Type::Var(v1), Type::Var(v2)) => substs.entry(v1).or_insert(v2) == &v2,
|
||||||
|
(
|
||||||
|
Type::Function(FunctionType {
|
||||||
|
args: args1,
|
||||||
|
ret: ret1,
|
||||||
|
}),
|
||||||
|
Type::Function(FunctionType {
|
||||||
|
args: args2,
|
||||||
|
ret: ret2,
|
||||||
|
}),
|
||||||
|
) => {
|
||||||
|
args1.len() == args2.len()
|
||||||
|
&& args1
|
||||||
|
.iter()
|
||||||
|
.zip(args2)
|
||||||
|
.all(|(a1, a2)| do_alpha_equiv(substs, a1, a2))
|
||||||
|
&& do_alpha_equiv(substs, ret1, ret2)
|
||||||
|
}
|
||||||
|
_ => lhs == rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut substs = HashMap::new();
|
||||||
|
do_alpha_equiv(&mut substs, self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Type<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Type::Int => f.write_str("int"),
|
Type::Int => f.write_str("int"),
|
||||||
Type::Float => f.write_str("float"),
|
Type::Float => f.write_str("float"),
|
||||||
Type::Bool => f.write_str("bool"),
|
Type::Bool => f.write_str("bool"),
|
||||||
Type::CString => f.write_str("cstring"),
|
Type::CString => f.write_str("cstring"),
|
||||||
|
Type::Var(v) => v.fmt(f),
|
||||||
Type::Function(ft) => ft.fmt(f),
|
Type::Function(ft) => ft.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn type_var(n: &str) -> Type<'static> {
|
||||||
|
Type::Var(Ident::try_from(n.to_owned()).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod alpha_equiv {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trivial() {
|
||||||
|
assert!(Type::Int.alpha_equiv(&Type::Int));
|
||||||
|
assert!(!Type::Int.alpha_equiv(&Type::Bool));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_type_var() {
|
||||||
|
assert!(type_var("a").alpha_equiv(&type_var("b")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_with_type_vars_equiv() {
|
||||||
|
assert!(Type::Function(FunctionType {
|
||||||
|
args: vec![type_var("a")],
|
||||||
|
ret: Box::new(type_var("b")),
|
||||||
|
})
|
||||||
|
.alpha_equiv(&Type::Function(FunctionType {
|
||||||
|
args: vec![type_var("b")],
|
||||||
|
ret: Box::new(type_var("a")),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_with_type_vars_non_equiv() {
|
||||||
|
assert!(!Type::Function(FunctionType {
|
||||||
|
args: vec![type_var("a")],
|
||||||
|
ret: Box::new(type_var("a")),
|
||||||
|
})
|
||||||
|
.alpha_equiv(&Type::Function(FunctionType {
|
||||||
|
args: vec![type_var("b")],
|
||||||
|
ret: Box::new(type_var("a")),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@ pub struct Check {
|
||||||
expr: Option<String>,
|
expr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_expr(expr: String) -> Result<Type> {
|
fn run_expr(expr: String) -> Result<Type<'static>> {
|
||||||
let (_, parsed) = parser::expr(&expr)?;
|
let (_, parsed) = parser::expr(&expr)?;
|
||||||
let hir_expr = tc::typecheck_expr(parsed)?;
|
let hir_expr = tc::typecheck_expr(parsed)?;
|
||||||
Ok(hir_expr.type_().clone())
|
Ok(hir_expr.type_().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_path(path: PathBuf) -> Result<Type> {
|
fn run_path(path: PathBuf) -> Result<Type<'static>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub(crate) mod env;
|
pub(crate) mod env;
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
pub(crate) mod namer;
|
||||||
|
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
|
pub use namer::{Namer, NamerOf};
|
||||||
|
|
122
src/common/namer.rs
Normal file
122
src/common/namer.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub struct Namer<T, F> {
|
||||||
|
make_name: F,
|
||||||
|
counter: u64,
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F> Namer<T, F> {
|
||||||
|
pub fn new(make_name: F) -> Self {
|
||||||
|
Namer {
|
||||||
|
make_name,
|
||||||
|
counter: 0,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Namer<String, Box<dyn Fn(u64) -> String>> {
|
||||||
|
pub fn with_prefix<T>(prefix: T) -> Self
|
||||||
|
where
|
||||||
|
T: Display + 'static,
|
||||||
|
{
|
||||||
|
Namer::new(move |i| format!("{}{}", prefix, i)).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_suffix<T>(suffix: T) -> Self
|
||||||
|
where
|
||||||
|
T: Display + 'static,
|
||||||
|
{
|
||||||
|
Namer::new(move |i| format!("{}{}", i, suffix)).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alphabetic() -> Self {
|
||||||
|
Namer::new(|i| {
|
||||||
|
if i <= 26 {
|
||||||
|
std::char::from_u32((i + 96) as u32).unwrap().to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
std::char::from_u32(((i % 26) + 96) as u32).unwrap(),
|
||||||
|
i - 26
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F> Namer<T, F>
|
||||||
|
where
|
||||||
|
F: Fn(u64) -> T,
|
||||||
|
{
|
||||||
|
pub fn make_name(&mut self) -> T {
|
||||||
|
self.counter += 1;
|
||||||
|
(self.make_name)(self.counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boxed(self) -> NamerOf<T>
|
||||||
|
where
|
||||||
|
F: 'static,
|
||||||
|
{
|
||||||
|
Namer {
|
||||||
|
make_name: Box::new(self.make_name),
|
||||||
|
counter: self.counter,
|
||||||
|
_phantom: self._phantom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<G, U>(self, f: G) -> NamerOf<U>
|
||||||
|
where
|
||||||
|
G: Fn(T) -> U + 'static,
|
||||||
|
T: 'static,
|
||||||
|
F: 'static,
|
||||||
|
{
|
||||||
|
Namer {
|
||||||
|
counter: self.counter,
|
||||||
|
make_name: Box::new(move |x| f((self.make_name)(x))),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NamerOf<T> = Namer<T, Box<dyn Fn(u64) -> T>>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefix() {
|
||||||
|
let mut namer = Namer::with_prefix("t");
|
||||||
|
assert_eq!(namer.make_name(), "t1");
|
||||||
|
assert_eq!(namer.make_name(), "t2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suffix() {
|
||||||
|
let mut namer = Namer::with_suffix("t");
|
||||||
|
assert_eq!(namer.make_name(), "1t");
|
||||||
|
assert_eq!(namer.make_name(), "2t");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alphabetic() {
|
||||||
|
let mut namer = Namer::alphabetic();
|
||||||
|
assert_eq!(namer.make_name(), "a");
|
||||||
|
assert_eq!(namer.make_name(), "b");
|
||||||
|
(0..25).for_each(|_| {
|
||||||
|
namer.make_name();
|
||||||
|
});
|
||||||
|
assert_eq!(namer.make_name(), "b2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_callback() {
|
||||||
|
let mut namer = Namer::new(|n| n + 1);
|
||||||
|
assert_eq!(namer.make_name(), 2);
|
||||||
|
assert_eq!(namer.make_name(), 3);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,10 @@ pub enum Error {
|
||||||
UndefinedVariable(Ident<'static>),
|
UndefinedVariable(Ident<'static>),
|
||||||
|
|
||||||
#[error("Unexpected type {actual}, expected type {expected}")]
|
#[error("Unexpected type {actual}, expected type {expected}")]
|
||||||
InvalidType { actual: Type, expected: Type },
|
InvalidType {
|
||||||
|
actual: Type<'static>,
|
||||||
|
expected: Type<'static>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
|
@ -115,7 +115,7 @@ impl<'a> Interpreter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval<'a>(expr: &'a Expr<'a, Type>) -> Result<Value> {
|
pub fn eval<'a>(expr: &'a Expr<'a, Type>) -> Result<Value<'a>> {
|
||||||
let mut interpreter = Interpreter::new();
|
let mut interpreter = Interpreter::new();
|
||||||
interpreter.eval(expr)
|
interpreter.eval(expr)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use BinaryOperator::*;
|
use BinaryOperator::*;
|
||||||
|
|
||||||
fn int_lit(i: u64) -> Box<Expr<'static, Type>> {
|
fn int_lit(i: u64) -> Box<Expr<'static, Type<'static>>> {
|
||||||
Box::new(Expr::Literal(Literal::Int(i), Type::Int))
|
Box::new(Expr::Literal(Literal::Int(i), Type::Int))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +168,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn function_call() {
|
fn function_call() {
|
||||||
let res = do_eval::<i64>("let id = fn x = x in id 1");
|
let res = do_eval::<i64>("let id = fn x = x in id 1");
|
||||||
assert_eq!(res, 1);
|
assert_eq!(res, 1);
|
||||||
|
|
|
@ -13,9 +13,9 @@ use crate::ast::{FunctionType, Ident, Type};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Function<'a> {
|
pub struct Function<'a> {
|
||||||
pub type_: FunctionType,
|
pub type_: FunctionType<'a>,
|
||||||
pub args: Vec<Ident<'a>>,
|
pub args: Vec<Ident<'a>>,
|
||||||
pub body: Expr<'a, Type>,
|
pub body: Expr<'a, Type<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(From, TryInto)]
|
#[derive(From, TryInto)]
|
||||||
|
@ -100,7 +100,7 @@ impl<'a> Val<'a> {
|
||||||
&'b T: TryFrom<&'b Self>,
|
&'b T: TryFrom<&'b Self>,
|
||||||
{
|
{
|
||||||
<&T>::try_from(self).map_err(|_| Error::InvalidType {
|
<&T>::try_from(self).map_err(|_| Error::InvalidType {
|
||||||
actual: self.type_(),
|
actual: self.type_().to_owned(),
|
||||||
expected: <T as TypeOf>::type_of(),
|
expected: <T as TypeOf>::type_of(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -109,8 +109,8 @@ impl<'a> Val<'a> {
|
||||||
match self {
|
match self {
|
||||||
Val::Function(f) if f.type_ == function_type => Ok(&f),
|
Val::Function(f) if f.type_ == function_type => Ok(&f),
|
||||||
_ => Err(Error::InvalidType {
|
_ => Err(Error::InvalidType {
|
||||||
actual: self.type_(),
|
actual: self.type_().to_owned(),
|
||||||
expected: Type::Function(function_type),
|
expected: Type::Function(function_type.to_owned()),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,29 +175,29 @@ impl<'a> Div for Value<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TypeOf {
|
pub trait TypeOf {
|
||||||
fn type_of() -> Type;
|
fn type_of() -> Type<'static>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeOf for i64 {
|
impl TypeOf for i64 {
|
||||||
fn type_of() -> Type {
|
fn type_of() -> Type<'static> {
|
||||||
Type::Int
|
Type::Int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeOf for bool {
|
impl TypeOf for bool {
|
||||||
fn type_of() -> Type {
|
fn type_of() -> Type<'static> {
|
||||||
Type::Bool
|
Type::Bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeOf for f64 {
|
impl TypeOf for f64 {
|
||||||
fn type_of() -> Type {
|
fn type_of() -> Type<'static> {
|
||||||
Type::Float
|
Type::Float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeOf for String {
|
impl TypeOf for String {
|
||||||
fn type_of() -> Type {
|
fn type_of() -> Type<'static> {
|
||||||
Type::CString
|
Type::CString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![feature(str_split_once)]
|
#![feature(str_split_once)]
|
||||||
|
#![feature(or_insert_with_key)]
|
||||||
|
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use nom::{
|
||||||
use pratt::{Affix, Associativity, PrattParser, Precedence};
|
use pratt::{Affix, Associativity, PrattParser, Precedence};
|
||||||
|
|
||||||
use crate::ast::{BinaryOperator, Binding, Expr, Fun, Literal, UnaryOperator};
|
use crate::ast::{BinaryOperator, Binding, Expr, Fun, Literal, UnaryOperator};
|
||||||
use crate::parser::{ident, type_};
|
use crate::parser::{arg, ident, type_};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum TokenTree<'a> {
|
enum TokenTree<'a> {
|
||||||
|
@ -274,7 +274,7 @@ named!(no_arg_call(&str) -> Expr, do_parse!(
|
||||||
named!(fun_expr(&str) -> Expr, do_parse!(
|
named!(fun_expr(&str) -> Expr, do_parse!(
|
||||||
tag!("fn")
|
tag!("fn")
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> args: separated_list0!(multispace1, ident)
|
>> args: separated_list0!(multispace1, arg)
|
||||||
>> multispace0
|
>> multispace0
|
||||||
>> char!('=')
|
>> char!('=')
|
||||||
>> multispace0
|
>> multispace0
|
||||||
|
@ -285,7 +285,7 @@ named!(fun_expr(&str) -> Expr, do_parse!(
|
||||||
})))
|
})))
|
||||||
));
|
));
|
||||||
|
|
||||||
named!(arg(&str) -> Expr, alt!(
|
named!(fn_arg(&str) -> Expr, alt!(
|
||||||
ident_expr |
|
ident_expr |
|
||||||
literal_expr |
|
literal_expr |
|
||||||
paren_expr
|
paren_expr
|
||||||
|
@ -294,7 +294,7 @@ named!(arg(&str) -> Expr, alt!(
|
||||||
named!(call_with_args(&str) -> Expr, do_parse!(
|
named!(call_with_args(&str) -> Expr, do_parse!(
|
||||||
fun: funcref
|
fun: funcref
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> args: separated_list1!(multispace1, arg)
|
>> args: separated_list1!(multispace1, fn_arg)
|
||||||
>> (Expr::Call {
|
>> (Expr::Call {
|
||||||
fun: Box::new(fun),
|
fun: Box::new(fun),
|
||||||
args
|
args
|
||||||
|
@ -326,7 +326,7 @@ named!(pub expr(&str) -> Expr, alt!(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::{Ident, Type};
|
use crate::ast::{Arg, Ident, Type};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use BinaryOperator::*;
|
use BinaryOperator::*;
|
||||||
use Expr::{BinaryOp, If, Let, UnaryOp};
|
use Expr::{BinaryOp, If, Let, UnaryOp};
|
||||||
|
@ -549,7 +549,7 @@ pub(crate) mod tests {
|
||||||
ident: Ident::try_from("id").unwrap(),
|
ident: Ident::try_from("id").unwrap(),
|
||||||
type_: None,
|
type_: None,
|
||||||
body: Expr::Fun(Box::new(Fun {
|
body: Expr::Fun(Box::new(Fun {
|
||||||
args: vec![Ident::try_from("x").unwrap()],
|
args: vec![Arg::try_from("x").unwrap()],
|
||||||
body: *ident_expr("x")
|
body: *ident_expr("x")
|
||||||
}))
|
}))
|
||||||
}],
|
}],
|
||||||
|
@ -586,7 +586,7 @@ pub(crate) mod tests {
|
||||||
ident: Ident::try_from("const_1").unwrap(),
|
ident: Ident::try_from("const_1").unwrap(),
|
||||||
type_: None,
|
type_: None,
|
||||||
body: Expr::Fun(Box::new(Fun {
|
body: Expr::Fun(Box::new(Fun {
|
||||||
args: vec![Ident::try_from("x").unwrap()],
|
args: vec![Arg::try_from("x").unwrap()],
|
||||||
body: Expr::Ascription {
|
body: Expr::Ascription {
|
||||||
expr: Box::new(Expr::Literal(Literal::Int(1))),
|
expr: Box::new(Expr::Literal(Literal::Int(1))),
|
||||||
type_: Type::Int,
|
type_: Type::Int,
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod macros;
|
||||||
mod expr;
|
mod expr;
|
||||||
mod type_;
|
mod type_;
|
||||||
|
|
||||||
use crate::ast::{Decl, Fun, Ident};
|
use crate::ast::{Arg, Decl, Fun, Ident};
|
||||||
pub use expr::expr;
|
pub use expr::expr;
|
||||||
pub use type_::type_;
|
pub use type_::type_;
|
||||||
|
|
||||||
|
@ -58,12 +58,33 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
named!(ascripted_arg(&str) -> Arg, do_parse!(
|
||||||
|
complete!(char!('(')) >>
|
||||||
|
multispace0 >>
|
||||||
|
ident: ident >>
|
||||||
|
multispace0 >>
|
||||||
|
complete!(char!(':')) >>
|
||||||
|
multispace0 >>
|
||||||
|
type_: type_ >>
|
||||||
|
multispace0 >>
|
||||||
|
complete!(char!(')')) >>
|
||||||
|
(Arg {
|
||||||
|
ident,
|
||||||
|
type_: Some(type_)
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
named!(arg(&str) -> Arg, alt!(
|
||||||
|
ident => { |ident| Arg {ident, type_: None}} |
|
||||||
|
ascripted_arg
|
||||||
|
));
|
||||||
|
|
||||||
named!(fun_decl(&str) -> Decl, do_parse!(
|
named!(fun_decl(&str) -> Decl, do_parse!(
|
||||||
complete!(tag!("fn"))
|
complete!(tag!("fn"))
|
||||||
>> multispace0
|
>> multispace0
|
||||||
>> name: ident
|
>> name: ident
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> args: separated_list0!(multispace1, ident)
|
>> args: separated_list0!(multispace1, arg)
|
||||||
>> multispace0
|
>> multispace0
|
||||||
>> char!('=')
|
>> char!('=')
|
||||||
>> multispace0
|
>> multispace0
|
||||||
|
@ -87,6 +108,8 @@ named!(pub toplevel(&str) -> Vec<Decl>, terminated!(many0!(decl), multispace0));
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
use crate::ast::{BinaryOperator, Expr, Literal, Type};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use expr::tests::ident_expr;
|
use expr::tests::ident_expr;
|
||||||
|
|
||||||
|
@ -105,6 +128,29 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ascripted_fn_args() {
|
||||||
|
test_parse!(ascripted_arg, "(x : int)");
|
||||||
|
let res = test_parse!(decl, "fn plus1 (x : int) = x + 1");
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Decl::Fun {
|
||||||
|
name: "plus1".try_into().unwrap(),
|
||||||
|
body: Fun {
|
||||||
|
args: vec![Arg {
|
||||||
|
ident: "x".try_into().unwrap(),
|
||||||
|
type_: Some(Type::Int),
|
||||||
|
}],
|
||||||
|
body: Expr::BinaryOp {
|
||||||
|
lhs: ident_expr("x"),
|
||||||
|
op: BinaryOperator::Add,
|
||||||
|
rhs: Box::new(Expr::Literal(Literal::Int(1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_decls() {
|
fn multiple_decls() {
|
||||||
let res = test_parse!(
|
let res = test_parse!(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use nom::character::complete::{multispace0, multispace1};
|
use nom::character::complete::{multispace0, multispace1};
|
||||||
use nom::{alt, delimited, do_parse, map, named, opt, separated_list0, tag, terminated, tuple};
|
use nom::{alt, delimited, do_parse, map, named, opt, separated_list0, tag, terminated, tuple};
|
||||||
|
|
||||||
|
use super::ident;
|
||||||
use crate::ast::{FunctionType, Type};
|
use crate::ast::{FunctionType, Type};
|
||||||
|
|
||||||
named!(function_type(&str) -> Type, do_parse!(
|
named!(function_type(&str) -> Type, do_parse!(
|
||||||
|
@ -29,6 +30,7 @@ named!(pub type_(&str) -> Type, alt!(
|
||||||
tag!("bool") => { |_| Type::Bool } |
|
tag!("bool") => { |_| Type::Bool } |
|
||||||
tag!("cstring") => { |_| Type::CString } |
|
tag!("cstring") => { |_| Type::CString } |
|
||||||
function_type |
|
function_type |
|
||||||
|
ident => { |id| Type::Var(id) } |
|
||||||
delimited!(
|
delimited!(
|
||||||
tuple!(tag!("("), multispace0),
|
tuple!(tag!("("), multispace0),
|
||||||
type_,
|
type_,
|
||||||
|
@ -38,7 +40,10 @@ named!(pub type_(&str) -> Type, alt!(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ast::Ident;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_types() {
|
fn simple_types() {
|
||||||
|
@ -103,4 +108,18 @@ mod tests {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_vars() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse!(type_, "fn x, y -> x"),
|
||||||
|
Type::Function(FunctionType {
|
||||||
|
args: vec![
|
||||||
|
Type::Var(Ident::try_from("x").unwrap()),
|
||||||
|
Type::Var(Ident::try_from("y").unwrap()),
|
||||||
|
],
|
||||||
|
ret: Box::new(Type::Var(Ident::try_from("x").unwrap())),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
272
src/tc/mod.rs
272
src/tc/mod.rs
|
@ -1,13 +1,16 @@
|
||||||
|
use bimap::BiMap;
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::result;
|
use std::{mem, result};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::ast::{self, hir, BinaryOperator, Ident, Literal};
|
use crate::ast::{self, hir, Arg, BinaryOperator, Ident, Literal};
|
||||||
use crate::common::env::Env;
|
use crate::common::env::Env;
|
||||||
|
use crate::common::{Namer, NamerOf};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -52,7 +55,7 @@ pub enum PrimType {
|
||||||
CString,
|
CString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrimType> for ast::Type {
|
impl<'a> From<PrimType> for ast::Type<'a> {
|
||||||
fn from(pr: PrimType) -> Self {
|
fn from(pr: PrimType) -> Self {
|
||||||
match pr {
|
match pr {
|
||||||
PrimType::Int => ast::Type::Int,
|
PrimType::Int => ast::Type::Int,
|
||||||
|
@ -88,22 +91,7 @@ pub enum Type {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<ast::Type> for Type {
|
impl<'a> TryFrom<Type> for ast::Type<'a> {
|
||||||
fn eq(&self, other: &ast::Type) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Type::Univ(_), _) => todo!(),
|
|
||||||
(Type::Exist(_), _) => false,
|
|
||||||
(Type::Nullary(_), _) => todo!(),
|
|
||||||
(Type::Prim(pr), ty) => ast::Type::from(*pr) == *ty,
|
|
||||||
(Type::Fun { args, ret }, ast::Type::Function(ft)) => {
|
|
||||||
*args == ft.args && (**ret).eq(&*ft.ret)
|
|
||||||
}
|
|
||||||
(Type::Fun { .. }, _) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Type> for ast::Type {
|
|
||||||
type Error = Type;
|
type Error = Type;
|
||||||
|
|
||||||
fn try_from(value: Type) -> result::Result<Self, Self::Error> {
|
fn try_from(value: Type) -> result::Result<Self, Self::Error> {
|
||||||
|
@ -142,33 +130,29 @@ impl Display for Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ast::Type> for Type {
|
|
||||||
fn from(type_: ast::Type) -> Self {
|
|
||||||
match type_ {
|
|
||||||
ast::Type::Int => INT,
|
|
||||||
ast::Type::Float => FLOAT,
|
|
||||||
ast::Type::Bool => BOOL,
|
|
||||||
ast::Type::CString => CSTRING,
|
|
||||||
ast::Type::Function(ast::FunctionType { args, ret }) => Type::Fun {
|
|
||||||
args: args.into_iter().map(Self::from).collect(),
|
|
||||||
ret: Box::new(Self::from(*ret)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Typechecker<'ast> {
|
struct Typechecker<'ast> {
|
||||||
ty_var_counter: u64,
|
ty_var_namer: NamerOf<TyVar>,
|
||||||
ctx: HashMap<TyVar, Type>,
|
ctx: HashMap<TyVar, Type>,
|
||||||
env: Env<Ident<'ast>, Type>,
|
env: Env<Ident<'ast>, Type>,
|
||||||
|
|
||||||
|
/// AST type var -> type
|
||||||
|
instantiations: Env<Ident<'ast>, Type>,
|
||||||
|
|
||||||
|
/// AST type-var -> universal TyVar
|
||||||
|
type_vars: RefCell<(BiMap<Ident<'ast>, TyVar>, NamerOf<Ident<'static>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ast> Typechecker<'ast> {
|
impl<'ast> Typechecker<'ast> {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ty_var_counter: 0,
|
ty_var_namer: Namer::new(TyVar).boxed(),
|
||||||
|
type_vars: RefCell::new((
|
||||||
|
Default::default(),
|
||||||
|
Namer::alphabetic().map(|n| Ident::try_from(n).unwrap()),
|
||||||
|
)),
|
||||||
ctx: Default::default(),
|
ctx: Default::default(),
|
||||||
env: Default::default(),
|
env: Default::default(),
|
||||||
|
instantiations: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +208,8 @@ impl<'ast> Typechecker<'ast> {
|
||||||
|ast::Binding { ident, type_, body }| -> Result<hir::Binding<Type>> {
|
|ast::Binding { ident, type_, body }| -> Result<hir::Binding<Type>> {
|
||||||
let body = self.tc_expr(body)?;
|
let body = self.tc_expr(body)?;
|
||||||
if let Some(type_) = type_ {
|
if let Some(type_) = type_ {
|
||||||
self.unify(body.type_(), &type_.into())?;
|
let type_ = self.type_from_ast_type(type_);
|
||||||
|
self.unify(body.type_(), &type_)?;
|
||||||
}
|
}
|
||||||
self.env.set(ident.clone(), body.type_().clone());
|
self.env.set(ident.clone(), body.type_().clone());
|
||||||
Ok(hir::Binding {
|
Ok(hir::Binding {
|
||||||
|
@ -265,19 +250,22 @@ impl<'ast> Typechecker<'ast> {
|
||||||
self.env.push();
|
self.env.push();
|
||||||
let args: Vec<_> = args
|
let args: Vec<_> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|id| {
|
.map(|Arg { ident, type_ }| {
|
||||||
let ty = self.fresh_ex();
|
let ty = match type_ {
|
||||||
self.env.set(id.clone(), ty.clone());
|
Some(t) => self.type_from_ast_type(t),
|
||||||
(id, ty)
|
None => self.fresh_ex(),
|
||||||
|
};
|
||||||
|
self.env.set(ident.clone(), ty.clone());
|
||||||
|
(ident, ty)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let body = self.tc_expr(body)?;
|
let body = self.tc_expr(body)?;
|
||||||
self.env.pop();
|
self.env.pop();
|
||||||
Ok(hir::Expr::Fun {
|
Ok(hir::Expr::Fun {
|
||||||
type_: Type::Fun {
|
type_: self.universalize(
|
||||||
args: args.iter().map(|(_, ty)| ty.clone()).collect(),
|
args.iter().map(|(_, ty)| ty.clone()).collect(),
|
||||||
ret: Box::new(body.type_().clone()),
|
body.type_().clone(),
|
||||||
},
|
),
|
||||||
args,
|
args,
|
||||||
body: Box::new(body),
|
body: Box::new(body),
|
||||||
})
|
})
|
||||||
|
@ -290,6 +278,7 @@ impl<'ast> Typechecker<'ast> {
|
||||||
ret: Box::new(ret_ty.clone()),
|
ret: Box::new(ret_ty.clone()),
|
||||||
};
|
};
|
||||||
let fun = self.tc_expr(*fun)?;
|
let fun = self.tc_expr(*fun)?;
|
||||||
|
self.instantiations.push();
|
||||||
self.unify(&ft, fun.type_())?;
|
self.unify(&ft, fun.type_())?;
|
||||||
let args = args
|
let args = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -300,6 +289,7 @@ impl<'ast> Typechecker<'ast> {
|
||||||
Ok(arg)
|
Ok(arg)
|
||||||
})
|
})
|
||||||
.try_collect()?;
|
.try_collect()?;
|
||||||
|
self.commit_instantiations();
|
||||||
Ok(hir::Expr::Call {
|
Ok(hir::Expr::Call {
|
||||||
fun: Box::new(fun),
|
fun: Box::new(fun),
|
||||||
args,
|
args,
|
||||||
|
@ -308,7 +298,8 @@ impl<'ast> Typechecker<'ast> {
|
||||||
}
|
}
|
||||||
ast::Expr::Ascription { expr, type_ } => {
|
ast::Expr::Ascription { expr, type_ } => {
|
||||||
let expr = self.tc_expr(*expr)?;
|
let expr = self.tc_expr(*expr)?;
|
||||||
self.unify(expr.type_(), &type_.into())?;
|
let type_ = self.type_from_ast_type(type_);
|
||||||
|
self.unify(expr.type_(), &type_)?;
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,8 +325,7 @@ impl<'ast> Typechecker<'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fresh_tv(&mut self) -> TyVar {
|
fn fresh_tv(&mut self) -> TyVar {
|
||||||
self.ty_var_counter += 1;
|
self.ty_var_namer.make_name()
|
||||||
TyVar(self.ty_var_counter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fresh_ex(&mut self) -> Type {
|
fn fresh_ex(&mut self) -> Type {
|
||||||
|
@ -343,29 +333,69 @@ impl<'ast> Typechecker<'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fresh_univ(&mut self) -> Type {
|
fn fresh_univ(&mut self) -> Type {
|
||||||
Type::Exist(self.fresh_tv())
|
Type::Univ(self.fresh_tv())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn universalize<'a>(&mut self, expr: hir::Expr<'a, Type>) -> hir::Expr<'a, Type> {
|
#[allow(clippy::redundant_closure)] // https://github.com/rust-lang/rust-clippy/issues/6903
|
||||||
// TODO
|
fn universalize(&mut self, args: Vec<Type>, ret: Type) -> Type {
|
||||||
expr
|
let mut vars = HashMap::new();
|
||||||
|
let mut universalize_type = move |ty| match ty {
|
||||||
|
Type::Exist(tv) if self.resolve_tv(tv).is_none() => vars
|
||||||
|
.entry(tv)
|
||||||
|
.or_insert_with_key(|tv| {
|
||||||
|
let ty = self.fresh_univ();
|
||||||
|
self.ctx.insert(*tv, ty.clone());
|
||||||
|
ty
|
||||||
|
})
|
||||||
|
.clone(),
|
||||||
|
_ => ty,
|
||||||
|
};
|
||||||
|
|
||||||
|
Type::Fun {
|
||||||
|
args: args.into_iter().map(|t| universalize_type(t)).collect(),
|
||||||
|
ret: Box::new(universalize_type(ret)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unify(&mut self, ty1: &Type, ty2: &Type) -> Result<Type> {
|
fn unify(&mut self, ty1: &Type, ty2: &Type) -> Result<Type> {
|
||||||
match (ty1, ty2) {
|
match (ty1, ty2) {
|
||||||
(Type::Prim(p1), Type::Prim(p2)) if p1 == p2 => Ok(ty2.clone()),
|
|
||||||
(Type::Exist(tv), ty) | (ty, Type::Exist(tv)) => match self.resolve_tv(*tv) {
|
(Type::Exist(tv), ty) | (ty, Type::Exist(tv)) => match self.resolve_tv(*tv) {
|
||||||
Some(existing_ty) if *ty == existing_ty => Ok(ty.clone()),
|
Some(existing_ty) if self.types_match(ty, &existing_ty) => Ok(ty.clone()),
|
||||||
Some(existing_ty) => Err(Error::TypeMismatch {
|
Some(var @ ast::Type::Var(_)) => {
|
||||||
expected: ty.clone(),
|
let var = self.type_from_ast_type(var);
|
||||||
actual: existing_ty.into(),
|
self.unify(&var, ty)
|
||||||
}),
|
}
|
||||||
|
Some(existing_ty) => match ty {
|
||||||
|
Type::Exist(_) => {
|
||||||
|
let rhs = self.type_from_ast_type(existing_ty);
|
||||||
|
self.unify(ty, &rhs)
|
||||||
|
}
|
||||||
|
_ => Err(Error::TypeMismatch {
|
||||||
|
expected: ty.clone(),
|
||||||
|
actual: self.type_from_ast_type(existing_ty),
|
||||||
|
}),
|
||||||
|
},
|
||||||
None => match self.ctx.insert(*tv, ty.clone()) {
|
None => match self.ctx.insert(*tv, ty.clone()) {
|
||||||
Some(existing) => self.unify(&existing, ty),
|
Some(existing) => self.unify(&existing, ty),
|
||||||
None => Ok(ty.clone()),
|
None => Ok(ty.clone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(Type::Univ(u1), Type::Univ(u2)) if u1 == u2 => Ok(ty2.clone()),
|
(Type::Univ(u1), Type::Univ(u2)) if u1 == u2 => Ok(ty2.clone()),
|
||||||
|
(Type::Univ(u), ty) | (ty, Type::Univ(u)) => {
|
||||||
|
let ident = self.name_univ(*u);
|
||||||
|
match self.instantiations.resolve(&ident) {
|
||||||
|
Some(existing_ty) if ty == existing_ty => Ok(ty.clone()),
|
||||||
|
Some(existing_ty) => Err(Error::TypeMismatch {
|
||||||
|
expected: ty.clone(),
|
||||||
|
actual: existing_ty.clone(),
|
||||||
|
}),
|
||||||
|
None => {
|
||||||
|
self.instantiations.set(ident, ty.clone());
|
||||||
|
Ok(ty.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Type::Prim(p1), Type::Prim(p2)) if p1 == p2 => Ok(ty2.clone()),
|
||||||
(
|
(
|
||||||
Type::Fun {
|
Type::Fun {
|
||||||
args: args1,
|
args: args1,
|
||||||
|
@ -395,18 +425,24 @@ impl<'ast> Typechecker<'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_expr(&self, expr: hir::Expr<'ast, Type>) -> Result<hir::Expr<'ast, ast::Type>> {
|
fn finalize_expr(
|
||||||
|
&self,
|
||||||
|
expr: hir::Expr<'ast, Type>,
|
||||||
|
) -> Result<hir::Expr<'ast, ast::Type<'ast>>> {
|
||||||
expr.traverse_type(|ty| self.finalize_type(ty))
|
expr.traverse_type(|ty| self.finalize_type(ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_decl(&self, decl: hir::Decl<'ast, Type>) -> Result<hir::Decl<'ast, ast::Type>> {
|
fn finalize_decl(
|
||||||
|
&self,
|
||||||
|
decl: hir::Decl<'ast, Type>,
|
||||||
|
) -> Result<hir::Decl<'ast, ast::Type<'ast>>> {
|
||||||
decl.traverse_type(|ty| self.finalize_type(ty))
|
decl.traverse_type(|ty| self.finalize_type(ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_type(&self, ty: Type) -> Result<ast::Type> {
|
fn finalize_type(&self, ty: Type) -> Result<ast::Type<'static>> {
|
||||||
match ty {
|
let ret = match ty {
|
||||||
Type::Exist(tv) => self.resolve_tv(tv).ok_or(Error::AmbiguousType(tv)),
|
Type::Exist(tv) => self.resolve_tv(tv).ok_or(Error::AmbiguousType(tv)),
|
||||||
Type::Univ(tv) => todo!(),
|
Type::Univ(tv) => Ok(ast::Type::Var(self.name_univ(tv))),
|
||||||
Type::Nullary(_) => todo!(),
|
Type::Nullary(_) => todo!(),
|
||||||
Type::Prim(pr) => Ok(pr.into()),
|
Type::Prim(pr) => Ok(pr.into()),
|
||||||
Type::Fun { args, ret } => Ok(ast::Type::Function(ast::FunctionType {
|
Type::Fun { args, ret } => Ok(ast::Type::Function(ast::FunctionType {
|
||||||
|
@ -416,23 +452,105 @@ impl<'ast> Typechecker<'ast> {
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
ret: Box::new(self.finalize_type(*ret)?),
|
ret: Box::new(self.finalize_type(*ret)?),
|
||||||
})),
|
})),
|
||||||
}
|
};
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_tv(&self, tv: TyVar) -> Option<ast::Type> {
|
fn resolve_tv(&self, tv: TyVar) -> Option<ast::Type<'static>> {
|
||||||
let mut res = &Type::Exist(tv);
|
let mut res = &Type::Exist(tv);
|
||||||
loop {
|
loop {
|
||||||
match res {
|
match res {
|
||||||
Type::Exist(tv) => {
|
Type::Exist(tv) => {
|
||||||
res = self.ctx.get(tv)?;
|
res = self.ctx.get(tv)?;
|
||||||
}
|
}
|
||||||
Type::Univ(_) => todo!(),
|
Type::Univ(tv) => {
|
||||||
|
let ident = self.name_univ(*tv);
|
||||||
|
if let Some(r) = self.instantiations.resolve(&ident) {
|
||||||
|
res = r;
|
||||||
|
} else {
|
||||||
|
break Some(ast::Type::Var(ident));
|
||||||
|
}
|
||||||
|
}
|
||||||
Type::Nullary(_) => todo!(),
|
Type::Nullary(_) => todo!(),
|
||||||
Type::Prim(pr) => break Some((*pr).into()),
|
Type::Prim(pr) => break Some((*pr).into()),
|
||||||
Type::Fun { args, ret } => todo!(),
|
Type::Fun { args, ret } => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_from_ast_type(&mut self, ast_type: ast::Type<'ast>) -> Type {
|
||||||
|
match ast_type {
|
||||||
|
ast::Type::Int => INT,
|
||||||
|
ast::Type::Float => FLOAT,
|
||||||
|
ast::Type::Bool => BOOL,
|
||||||
|
ast::Type::CString => CSTRING,
|
||||||
|
ast::Type::Function(ast::FunctionType { args, ret }) => Type::Fun {
|
||||||
|
args: args
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| self.type_from_ast_type(t))
|
||||||
|
.collect(),
|
||||||
|
ret: Box::new(self.type_from_ast_type(*ret)),
|
||||||
|
},
|
||||||
|
ast::Type::Var(id) => Type::Univ({
|
||||||
|
let opt_tv = { self.type_vars.borrow_mut().0.get_by_left(&id).copied() };
|
||||||
|
opt_tv.unwrap_or_else(|| {
|
||||||
|
let tv = self.fresh_tv();
|
||||||
|
self.type_vars
|
||||||
|
.borrow_mut()
|
||||||
|
.0
|
||||||
|
.insert_no_overwrite(id, tv)
|
||||||
|
.unwrap();
|
||||||
|
tv
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_univ(&self, tv: TyVar) -> Ident<'static> {
|
||||||
|
let mut vars = self.type_vars.borrow_mut();
|
||||||
|
vars.0
|
||||||
|
.get_by_right(&tv)
|
||||||
|
.map(Ident::to_owned)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let name = vars.1.make_name();
|
||||||
|
vars.0.insert_no_overwrite(name.clone(), tv).unwrap();
|
||||||
|
name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_instantiations(&mut self) {
|
||||||
|
let mut ctx = mem::take(&mut self.ctx);
|
||||||
|
for (_, v) in ctx.iter_mut() {
|
||||||
|
if let Type::Univ(tv) = v {
|
||||||
|
if let Some(concrete) = self.instantiations.resolve(&self.name_univ(*tv)) {
|
||||||
|
*v = concrete.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ctx = ctx;
|
||||||
|
self.instantiations.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn types_match(&self, type_: &Type, ast_type: &ast::Type<'ast>) -> bool {
|
||||||
|
match (type_, ast_type) {
|
||||||
|
(Type::Univ(u), ast::Type::Var(v)) => {
|
||||||
|
Some(u) == self.type_vars.borrow().0.get_by_left(v)
|
||||||
|
}
|
||||||
|
(Type::Univ(_), _) => false,
|
||||||
|
(Type::Exist(_), _) => false,
|
||||||
|
(Type::Nullary(_), _) => todo!(),
|
||||||
|
(Type::Prim(pr), ty) => ast::Type::from(*pr) == *ty,
|
||||||
|
(Type::Fun { args, ret }, ast::Type::Function(ft)) => {
|
||||||
|
args.len() == ft.args.len()
|
||||||
|
&& args
|
||||||
|
.iter()
|
||||||
|
.zip(&ft.args)
|
||||||
|
.all(|(a1, a2)| self.types_match(a1, &a2))
|
||||||
|
&& self.types_match(&*ret, &*ft.ret)
|
||||||
|
}
|
||||||
|
(Type::Fun { .. }, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typecheck_expr(expr: ast::Expr) -> Result<hir::Expr<ast::Type>> {
|
pub fn typecheck_expr(expr: ast::Expr) -> Result<hir::Expr<ast::Type>> {
|
||||||
|
@ -446,8 +564,10 @@ pub fn typecheck_toplevel(decls: Vec<ast::Decl>) -> Result<Vec<hir::Decl<ast::Ty
|
||||||
decls
|
decls
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|decl| {
|
.map(|decl| {
|
||||||
let decl = typechecker.tc_decl(decl)?;
|
let hir_decl = typechecker.tc_decl(decl)?;
|
||||||
typechecker.finalize_decl(decl)
|
let res = typechecker.finalize_decl(hir_decl)?;
|
||||||
|
typechecker.ctx.clear();
|
||||||
|
Ok(res)
|
||||||
})
|
})
|
||||||
.try_collect()
|
.try_collect()
|
||||||
}
|
}
|
||||||
|
@ -462,7 +582,13 @@ mod tests {
|
||||||
let parsed_expr = test_parse!(expr, $expr);
|
let parsed_expr = test_parse!(expr, $expr);
|
||||||
let parsed_type = test_parse!(type_, $type);
|
let parsed_type = test_parse!(type_, $type);
|
||||||
let res = typecheck_expr(parsed_expr).unwrap_or_else(|e| panic!("{}", e));
|
let res = typecheck_expr(parsed_expr).unwrap_or_else(|e| panic!("{}", e));
|
||||||
assert_eq!(res.type_(), &parsed_type);
|
assert!(
|
||||||
|
res.type_().alpha_equiv(&parsed_type),
|
||||||
|
"{} inferred type {}, but expected {}",
|
||||||
|
$expr,
|
||||||
|
res.type_(),
|
||||||
|
$type
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,9 +627,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn generic_function() {
|
fn generic_function() {
|
||||||
assert_type!("fn x = x", "fn x, y -> x");
|
assert_type!("fn x = x", "fn x -> x");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -517,6 +642,11 @@ mod tests {
|
||||||
assert_type!("fn x = x + 1", "fn int -> int");
|
assert_type!("fn x = x + 1", "fn int -> int");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arg_ascriptions() {
|
||||||
|
assert_type!("fn (x: int) = x", "fn int -> int");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn call_concrete_function() {
|
fn call_concrete_function() {
|
||||||
assert_type!("(fn x = x + 1) 2", "int");
|
assert_type!("(fn x = x + 1) 2", "int");
|
||||||
|
|
41
tests/compile.rs
Normal file
41
tests/compile.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate_root::root;
|
||||||
|
|
||||||
|
const FIXTURES: &[(&str, i32)] = &[("simple", 5), ("functions", 9)];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_and_run_files() {
|
||||||
|
let ach = root().unwrap().join("ach");
|
||||||
|
|
||||||
|
for (fixture, exit_code) in FIXTURES {
|
||||||
|
println!(">>> Testing: {}", fixture);
|
||||||
|
|
||||||
|
println!(" Running: `make {}`", fixture);
|
||||||
|
assert!(
|
||||||
|
Command::new("make")
|
||||||
|
.arg(fixture)
|
||||||
|
.current_dir(&ach)
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap()
|
||||||
|
.success(),
|
||||||
|
"make failed"
|
||||||
|
);
|
||||||
|
|
||||||
|
let out_path = ach.join(fixture);
|
||||||
|
println!(" Running: `{}`", out_path.to_str().unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
Command::new(out_path)
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap()
|
||||||
|
.code()
|
||||||
|
.unwrap(),
|
||||||
|
*exit_code,
|
||||||
|
);
|
||||||
|
println!(" OK");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue