mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Got more basic stuff working
This commit is contained in:
parent
c635da7e0c
commit
7e1ee8f2d5
10 changed files with 195 additions and 79 deletions
|
@ -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)
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
50
src/pretty_print_types.rs
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
src/solve.rs
26
src/solve.rs
|
@ -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")
|
||||
|
||||
|
|
18
src/subs.rs
18
src/subs.rs
|
@ -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,
|
||||
}
|
||||
|
|
24
src/types.rs
24
src/types.rs
|
@ -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,
|
||||
}
|
||||
|
|
10
src/unify.rs
10
src/unify.rs
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue