Got more basic stuff working

This commit is contained in:
Richard Feldman 2019-08-26 22:38:44 -04:00
parent c635da7e0c
commit 7e1ee8f2d5
10 changed files with 195 additions and 79 deletions

View file

@ -3,7 +3,7 @@ use canonicalize::Expr::{self, *};
use collections::{ImMap, MutMap};
use region::{Located, Region};
use subs::{Variable, Subs};
use types::{Expected, Expected::*, Builtin, LetConstraint};
use types::{Expected, Expected::*, LetConstraint, Reason};
use types::Type::{self, *};
use types::Constraint::{self, *};
@ -25,20 +25,59 @@ pub fn constrain(
expected: Expected<Type>,
) -> Constraint {
let region = loc_expr.region;
let builtin = |b| -> Constraint { Eq(region, Builtin(b), expected) };
match loc_expr.value {
Int(_) => { builtin(Builtin::Int) },
Frac(_, _) => { builtin(Builtin::Frac) },
Approx(_) => { builtin(Builtin::Approx) },
Str(_) => { builtin(Builtin::Str) },
EmptyStr => { builtin(Builtin::Str) },
EmptyRecord => { builtin(Builtin::EmptyRecord) },
InterpolatedStr(_, _) => { builtin(Builtin::Str) },
Int(_) => { Eq(num(subs.mk_flex_var()), expected, region) },
Frac(_, _) => { fractional(subs, expected, region) },
Approx(_) => { fractional(subs, expected, region) },
Str(_) => { Eq(string(), expected, region) },
EmptyStr => { Eq(string(), expected, region) },
InterpolatedStr(_, _) => { Eq(string(), expected, region) },
EmptyRecord => { Eq(EmptyRec, expected, region) },
_ => { panic!("TODO constraints") }
}
}
fn string() -> Type {
builtin_type("String", "String", Vec::new())
}
fn num(var: Variable) -> Type {
builtin_type("Num", "Num", vec![Type::Variable(var)])
}
fn fractional(subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint {
// We'll make a Num var1 and a Fractional var2,
// and then add a constraint that var1 needs to equal Fractional var2
let num_var = subs.mk_flex_var(); // Num var1
let fractional_var = subs.mk_flex_var(); // Fractional var2
let fractional_type =
Type::Apply(
"Num".to_string(),
"Fractional".to_string(),
vec![Type::Variable(fractional_var)]
);
let num_var_type = Type::Variable(num_var);
let num_type =
Type::Apply(
"Num".to_string(),
"Num".to_string(),
vec![num_var_type.clone()]
);
let expected_fractional =
ForReason(Reason::FractionalLiteral, fractional_type, region.clone());
And(vec![
Eq(num_type, expected, region.clone()),
Eq(num_var_type, expected_fractional, region),
])
}
fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
Type::Apply(module_name.to_string(), type_name.to_string(), args)
}
pub fn constrain_def(
loc_pattern: Located<Pattern>,
loc_expr: Located<Expr>,
@ -101,7 +140,7 @@ pub fn constrain_procedure(
header_constraint,
body_constraint
})),
Eq(region, args.typ, expected)
Eq(args.typ, expected, region)
])
}

View file

@ -7,15 +7,13 @@ use collections::{ImMap, MutMap};
use solve::solve;
use constrain::constrain;
pub fn infer_expr(loc_expr: Located<Expr>, procedures: MutMap<Symbol, Procedure>) -> Content {
let mut subs = Subs::new();
pub fn infer_expr(subs: &mut Subs, loc_expr: Located<Expr>, procedures: MutMap<Symbol, Procedure>) -> Content {
let bound_vars = ImMap::default();
let variable = subs.mk_flex_var();
let expected = NoExpectation(Variable(variable));
let constraint = constrain(bound_vars, &mut subs, loc_expr, expected);
let constraint = constrain(bound_vars, subs, loc_expr, expected);
solve(&mut subs, constraint);
solve(subs, constraint);
subs.get(variable).content
}

View file

@ -16,6 +16,7 @@ pub mod constrain;
pub mod solve;
pub mod unify;
pub mod infer;
pub mod pretty_print_types;
extern crate im_rc;
extern crate fraction;

50
src/pretty_print_types.rs Normal file
View file

@ -0,0 +1,50 @@
use subs::{Subs, Content, FlatType};
static WILDCARD: &'static str = "*";
static EMPTY_RECORD: &'static str = "{}";
pub fn content_to_string(content: Content, subs: &mut Subs) -> String {
let mut buf = String::new();
write_content(content, subs, &mut buf, false);
buf
}
fn write_content(content: Content, subs: &mut Subs, buf: &mut String, use_parens: bool) {
use subs::Content::*;
match content {
FlexVar(Some(name)) => buf.push_str(&name),
FlexVar(None) => buf.push_str(WILDCARD),
RigidVar(name) => buf.push_str(&name),
Structure(flat_type) => write_flat_type(flat_type, subs, buf, use_parens),
Error => buf.push_str("<type mismatch>")
}
}
fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_parens: bool) {
use subs::FlatType::*;
match flat_type {
Apply(module_name, type_name, vars) => {
if use_parens {
buf.push_str("(");
}
buf.push_str(&format!("{}.{}", module_name, type_name));
for var in vars {
buf.push_str(" ");
write_content(subs.get(var).content, subs, buf, true);
}
if use_parens {
buf.push_str(")");
}
},
EmptyRecord => buf.push_str(EMPTY_RECORD),
Func(_, _) => panic!("TODO write_flat_type for Func")
}
}

View file

@ -1,6 +1,6 @@
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Region {
pub start_line: u32,
pub start_col: u32,
@ -9,6 +9,22 @@ pub struct Region {
pub end_col: u32,
}
impl fmt::Debug for Region {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.start_line == 0 && self.start_col == 0 && self.end_line == 0 && self.end_col == 0 {
// In tests, it's super common to set all Located values to 0.
// Also in tests, we don't want to bother printing the locations
// because it makes failed assertions much harder to read.
write!(f, "")
} else {
write!(f, "|L {}, C {} - L {}, C {}|",
self.start_line, self.start_col,
self.end_line, self.end_col,
)
}
}
}
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Located<T> {
pub region: Region,
@ -46,11 +62,7 @@ where T: fmt::Debug
// because it makes failed assertions much harder to read.
self.value.fmt(f)
} else {
write!(f, "|L {}, C {} - L {}, C {}| {:?}",
region.start_line, region.start_col,
region.end_line, region.end_col,
self.value
)
write!(f, "{:?} {:?}", region, self.value)
}
}
}
}

View file

@ -16,12 +16,12 @@
use subs::{Subs, Variable, Descriptor, Content, FlatType};
use types::Constraint::{self, *};
use types::Type::{self, *};
use types::Builtin;
pub fn solve(subs: &mut Subs, constraint: Constraint) {
println!("\nSolving:\n\n\t{:?}\n\n", constraint);
match constraint {
True => (),
Eq(region, typ, expected_type) => {
Eq(typ, expected_type, region) => {
let actual = type_to_variable(subs, typ);
let expected = type_to_variable(subs, expected_type.unwrap());
@ -93,15 +93,21 @@ pub fn solve(subs: &mut Subs, constraint: Constraint) {
fn type_to_variable(subs: &mut Subs, typ: Type) -> Variable {
match typ {
Variable(var) => var,
Builtin(builtin) => {
match builtin {
Builtin::EmptyRecord => {
let content = Content::Structure(FlatType::EmptyRecord);
Apply(module_name, name, args) => {
let arg_vars: Vec<Variable> =
args.into_iter()
.map(|arg| type_to_variable(subs, arg))
.collect();
subs.fresh(Descriptor::from(content))
},
_ => panic!("type_to_variable builtin")
}
let flat_type = FlatType::Apply(module_name, name, arg_vars);
let content = Content::Structure(flat_type);
subs.fresh(Descriptor::from(content))
},
EmptyRec => {
let content = Content::Structure(FlatType::EmptyRecord);
subs.fresh(Descriptor::from(content))
},
_ => panic!("TODO type_to_var")

View file

@ -1,14 +1,28 @@
use ena::unify::{UnificationTable, UnifyKey, UnifyValue, InPlace, NoError};
use canonicalize::Symbol;
use std::fmt;
use unify;
pub struct Subs {
utable: UnificationTable<InPlace<Variable>>
}
#[derive(Copy, Debug, PartialEq, Eq, Clone)]
#[derive(Copy, PartialEq, Eq, Clone)]
pub struct Variable(u32);
impl Variable {
pub fn new_for_testing_only(num: u32) -> Self {
// This is a hack that should only ever be used for testing!
Variable(num)
}
}
impl fmt::Debug for Variable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl UnifyKey for Variable {
type Value = Descriptor;
@ -116,7 +130,7 @@ pub enum Content {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlatType {
Apply(Symbol, Vec<Variable>),
Apply(String /* module name */, String /* type name */, Vec<Variable>),
Func(Variable, Variable),
EmptyRecord,
}

View file

@ -9,12 +9,11 @@ type ModuleName = String;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Type {
/// One of the concrete built-in types (e.g. Int)
Builtin(Builtin),
EmptyRec,
/// A function. The types of its arguments, then the type of its return value.
Function(Vec<Type>, Box<Type>),
/// Applying a type to some arguments (e.g. Map.Map String Int)
Apply(ModuleName, Box<Type>, Vec<Type>),
Apply(ModuleName, String, Vec<Type>),
Variable(Variable),
/// A Blank in the editor (a "typed hole" as they're also known)
Blank,
@ -22,32 +21,37 @@ pub enum Type {
Erroneous(Problem),
}
#[derive(Debug)]
pub enum Expected<T> {
NoExpectation(T),
ForReason(Region, Reason, T),
ForReason(Reason, T, Region),
}
impl<T> Expected<T> {
pub fn unwrap(self) -> T {
match self {
Expected::NoExpectation(val) => val,
Expected::ForReason(_, _, val) => val
Expected::ForReason(_, val, _) => val
}
}
}
#[derive(Debug)]
pub enum Reason {
OperatorLeftArg(Operator),
OperatorRightArg(Operator),
FractionalLiteral
}
#[derive(Debug)]
pub enum Constraint {
Eq(Region, Type, Expected<Type>),
Eq(Type, Expected<Type>, Region),
True, // Used for things that always unify, e.g. blanks and runtime errors
Let(Box<LetConstraint>),
And(Vec<Constraint>)
}
#[derive(Debug)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
@ -68,11 +72,3 @@ pub enum Problem {
CircularType,
CanonicalizationProblem
}
/// Built-in types
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Builtin {
Str, Int, Frac, Approx,
EmptyRecord,
}

View file

@ -2,7 +2,7 @@ use subs::{Descriptor, FlatType};
use subs::Content::{self, *};
pub fn unify(left: &Descriptor, right: &Descriptor) -> Descriptor {
match left.content {
let answer = match left.content {
FlexVar(ref opt_name) => {
unify_flex(opt_name, &right.content)
},
@ -16,7 +16,11 @@ pub fn unify(left: &Descriptor, right: &Descriptor) -> Descriptor {
// Error propagates. Whatever we're comparing it to doesn't matter!
from_content(Error)
}
}
};
println!("\nUnifying:\n\n\t{:?}\n\n\t{:?}\n\n\t-----\n\n\t{:?}\n\n", left.content, right.content, answer.content);
answer
}
#[inline(always)]
@ -44,7 +48,7 @@ fn unify_structure(flat_type: &FlatType, other: &Content) -> Descriptor {
#[inline(always)]
fn unify_flat_type(left: &FlatType, right: &FlatType) -> Descriptor {
panic!("TODO");
// case (flatType, otherFlatType) of
// case (flatType, otherFlatType) oa
// (App1 home name args, App1 otherHome otherName otherArgs) | home == otherHome && name == otherName ->
// Unify $ \vars ok err ->
// let

View file

@ -14,19 +14,24 @@ mod test_infer {
use roc::collections::{MutMap, ImMap};
use roc::types::{Type, Problem};
use roc::types::Type::*;
use roc::types::Builtin::*;
use roc::subs::Content::{self, *};
use roc::subs::FlatType;
use roc::subs::{FlatType, Variable};
use roc::subs::Subs;
use roc::region::{Located, Region};
use roc::infer::infer_expr;
use roc::pretty_print_types::content_to_string;
// HELPERS
fn infer(src: &str) -> Content {
fn infer_eq(src: &str, expected: &str) {
let (expr, procedures) = can_expr(src);
let mut subs = Subs::new();
infer_expr(loc(expr), procedures)
let content = infer_expr(&mut subs, loc(expr), procedures);
let actual_str = content_to_string(content, &mut subs);
assert_eq!(actual_str, expected.to_string());
}
fn can_expr(expr_str: &str) -> (Expr, MutMap<Symbol, Procedure>) {
@ -54,37 +59,28 @@ mod test_infer {
(loc_expr.value, procedures)
}
// fn box_var(var_id: TypeVarId) -> Box<Type> {
// Box::new(TypeVar(var_id))
// }
fn apply(module_name: &str, type_name: &str, args: Vec<Variable>) -> Content {
Structure(FlatType::Apply(module_name.to_string(), type_name.to_string(), args))
}
// fn var(var_id: TypeVarId) -> Type {
// TypeVar(var_id)
// }
fn var(num: u32) -> Variable {
Variable::new_for_testing_only(num)
}
#[test]
fn infer_empty_record() {
assert_eq!(
infer("{}"),
Structure(FlatType::EmptyRecord)
);
infer_eq("{}", "{}");
}
// #[test]
// fn infer_int() {
// assert_eq!(
// infer("5"),
// Structure(FlatType::EmptyRecord)
// );
// }
#[test]
fn infer_int() {
infer_eq("5", "Num.Num *");
}
// #[test]
// fn infer_frac() {
// assert_eq!(
// infer("0.5"),
// Builtin(Frac)
// );
// }
#[test]
fn infer_fractional() {
infer_eq("0.5", "Num.Num (Num.Fractional *)");
}
// #[test]
// fn infer_approx() {