Checkpoint

This commit is contained in:
2023-10-14 16:39:16 -07:00
parent 87d027bf8d
commit 71228b9e09
18 changed files with 402 additions and 78 deletions

View File

@@ -122,7 +122,7 @@ impl<M: Module> Backend<M> {
// state, it's easier to just include them. // state, it's easier to just include them.
for item in program.items.drain(..) { for item in program.items.drain(..) {
match item { match item {
TopLevel::Function(_, _, _) => unimplemented!(), TopLevel::Function(_, _, _, _) => unimplemented!(),
// Print statements are fairly easy to compile: we just lookup the // Print statements are fairly easy to compile: we just lookup the
// output buffer, the address of the string to print, and the value // output buffer, the address of the string to print, and the value

View File

@@ -46,6 +46,8 @@ pub use value::Value;
use crate::backend::BackendError; use crate::backend::BackendError;
use self::primtype::UnknownPrimType;
/// All of the errors that can happen trying to evaluate an NGR program. /// All of the errors that can happen trying to evaluate an NGR program.
/// ///
/// This is yet another standard [`thiserror::Error`] type, but with the /// This is yet another standard [`thiserror::Error`] type, but with the
@@ -71,9 +73,13 @@ pub enum EvalError {
ExitCode(std::process::ExitStatus), ExitCode(std::process::ExitStatus),
#[error("Unexpected output at runtime: {0}")] #[error("Unexpected output at runtime: {0}")]
RuntimeOutput(String), RuntimeOutput(String),
#[error("Cannot cast to function type: {0}")]
CastToFunction(String),
#[error(transparent)]
UnknownPrimType(#[from] UnknownPrimType),
} }
impl PartialEq for EvalError { impl PartialEq<EvalError> for EvalError {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match self { match self {
EvalError::Lookup(a) => match other { EvalError::Lookup(a) => match other {
@@ -115,6 +121,16 @@ impl PartialEq for EvalError {
EvalError::RuntimeOutput(b) => a == b, EvalError::RuntimeOutput(b) => a == b,
_ => false, _ => false,
}, },
EvalError::CastToFunction(a) => match other {
EvalError::CastToFunction(b) => a == b,
_ => false,
},
EvalError::UnknownPrimType(a) => match other {
EvalError::UnknownPrimType(b) => a == b,
_ => false,
},
} }
} }
} }

View File

@@ -1,6 +1,8 @@
use crate::eval::primtype::PrimitiveType; use crate::eval::primtype::PrimitiveType;
use crate::eval::value::Value; use crate::eval::value::Value;
use super::primtype::{UnknownPrimType, ValuePrimitiveTypeError};
/// Errors that can occur running primitive operations in the evaluators. /// Errors that can occur running primitive operations in the evaluators.
#[derive(Clone, Debug, PartialEq, thiserror::Error)] #[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub enum PrimOpError { pub enum PrimOpError {
@@ -14,7 +16,7 @@ pub enum PrimOpError {
/// This variant covers when an operator must take a particular /// This variant covers when an operator must take a particular
/// type, but the user has provided a different one. /// type, but the user has provided a different one.
#[error("Bad type for operator {0}: {1}")] #[error("Bad type for operator {0}: {1}")]
BadTypeFor(&'static str, Value), BadTypeFor(String, Value),
/// Probably obvious from the name, but just to be very clear: this /// Probably obvious from the name, but just to be very clear: this
/// happens when you pass three arguments to a two argument operator, /// happens when you pass three arguments to a two argument operator,
/// etc. Technically that's a type error of some sort, but we split /// etc. Technically that's a type error of some sort, but we split
@@ -28,8 +30,10 @@ pub enum PrimOpError {
from: PrimitiveType, from: PrimitiveType,
to: PrimitiveType, to: PrimitiveType,
}, },
#[error("Unknown primitive type {0}")] #[error(transparent)]
UnknownPrimType(String), UnknownPrimType(#[from] UnknownPrimType),
#[error(transparent)]
ValuePrimitiveTypeError(#[from] ValuePrimitiveTypeError),
} }
// Implementing primitives in an interpreter like this is *super* tedious, // Implementing primitives in an interpreter like this is *super* tedious,
@@ -63,7 +67,7 @@ impl Value {
Value::I16(x) => Ok(Value::I16(x.wrapping_neg())), Value::I16(x) => Ok(Value::I16(x.wrapping_neg())),
Value::I32(x) => Ok(Value::I32(x.wrapping_neg())), Value::I32(x) => Ok(Value::I32(x.wrapping_neg())),
Value::I64(x) => Ok(Value::I64(x.wrapping_neg())), Value::I64(x) => Ok(Value::I64(x.wrapping_neg())),
_ => Err(PrimOpError::BadTypeFor("-", value.clone())), _ => Err(PrimOpError::BadTypeFor("-".to_string(), value.clone())),
}, },
_ => Err(PrimOpError::BadArgCount(operation.to_owned(), 1)), _ => Err(PrimOpError::BadArgCount(operation.to_owned(), 1)),
} }
@@ -135,6 +139,9 @@ impl Value {
right.clone(), right.clone(),
)), )),
}, },
Value::Function(_, _) => {
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone()))
}
} }
} }

View File

@@ -31,17 +31,28 @@ impl Display for PrimitiveType {
} }
} }
impl<'a> From<&'a Value> for PrimitiveType { #[derive(Clone, Debug, PartialEq, thiserror::Error)]
fn from(value: &Value) -> Self { pub enum ValuePrimitiveTypeError {
#[error("Could not convert function value to primitive type (possible function name: {0:?}")]
CannotConvertFunction(Option<String>),
}
impl<'a> TryFrom<&'a Value> for PrimitiveType {
type Error = ValuePrimitiveTypeError;
fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
match value { match value {
Value::I8(_) => PrimitiveType::I8, Value::I8(_) => Ok(PrimitiveType::I8),
Value::I16(_) => PrimitiveType::I16, Value::I16(_) => Ok(PrimitiveType::I16),
Value::I32(_) => PrimitiveType::I32, Value::I32(_) => Ok(PrimitiveType::I32),
Value::I64(_) => PrimitiveType::I64, Value::I64(_) => Ok(PrimitiveType::I64),
Value::U8(_) => PrimitiveType::U8, Value::U8(_) => Ok(PrimitiveType::U8),
Value::U16(_) => PrimitiveType::U16, Value::U16(_) => Ok(PrimitiveType::U16),
Value::U32(_) => PrimitiveType::U32, Value::U32(_) => Ok(PrimitiveType::U32),
Value::U64(_) => PrimitiveType::U64, Value::U64(_) => Ok(PrimitiveType::U64),
Value::Function(name, _) => {
Err(ValuePrimitiveTypeError::CannotConvertFunction(name.clone()))
}
} }
} }
} }
@@ -61,8 +72,14 @@ impl From<ConstantType> for PrimitiveType {
} }
} }
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
pub enum UnknownPrimType {
#[error("Could not convert '{0}' into a primitive type")]
UnknownPrimType(String),
}
impl FromStr for PrimitiveType { impl FromStr for PrimitiveType {
type Err = PrimOpError; type Err = UnknownPrimType;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
@@ -74,7 +91,7 @@ impl FromStr for PrimitiveType {
"u16" => Ok(PrimitiveType::U16), "u16" => Ok(PrimitiveType::U16),
"u32" => Ok(PrimitiveType::U32), "u32" => Ok(PrimitiveType::U32),
"u64" => Ok(PrimitiveType::U64), "u64" => Ok(PrimitiveType::U64),
_ => Err(PrimOpError::UnknownPrimType(s.to_string())), _ => Err(UnknownPrimType::UnknownPrimType(s.to_owned())),
} }
} }
} }
@@ -152,7 +169,7 @@ impl PrimitiveType {
(PrimitiveType::I64, Value::I64(x)) => Ok(Value::I64(*x)), (PrimitiveType::I64, Value::I64(x)) => Ok(Value::I64(*x)),
_ => Err(PrimOpError::UnsafeCast { _ => Err(PrimOpError::UnsafeCast {
from: source.into(), from: PrimitiveType::try_from(source)?,
to: *self, to: *self,
}), }),
} }

View File

@@ -1,11 +1,13 @@
use std::fmt::Display; use super::EvalError;
use std::fmt;
use std::rc::Rc;
/// Values in the interpreter. /// Values in the interpreter.
/// ///
/// Yes, this is yet another definition of a structure called `Value`, which /// Yes, this is yet another definition of a structure called `Value`, which
/// are almost entirely identical. However, it's nice to have them separated /// are almost entirely identical. However, it's nice to have them separated
/// by type so that we don't mix them up. /// by type so that we don't mix them up.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone)]
pub enum Value { pub enum Value {
I8(i8), I8(i8),
I16(i16), I16(i16),
@@ -15,19 +17,79 @@ pub enum Value {
U16(u16), U16(u16),
U32(u32), U32(u32),
U64(u64), U64(u64),
Function(
Option<String>,
Rc<dyn Fn(Vec<Value>) -> Result<Value, EvalError>>,
),
} }
impl Display for Value { fn format_value(value: &Value, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match value {
Value::I8(x) => write!(f, "{}i8", x),
Value::I16(x) => write!(f, "{}i16", x),
Value::I32(x) => write!(f, "{}i32", x),
Value::I64(x) => write!(f, "{}i64", x),
Value::U8(x) => write!(f, "{}u8", x),
Value::U16(x) => write!(f, "{}u16", x),
Value::U32(x) => write!(f, "{}u32", x),
Value::U64(x) => write!(f, "{}u64", x),
Value::Function(Some(name), _) => write!(f, "<function {}>", name),
Value::Function(None, _) => write!(f, "<function>"),
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_value(self, f)
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_value(self, f)
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match self { match self {
Value::I8(x) => write!(f, "{}i8", x), Value::I8(x) => match other {
Value::I16(x) => write!(f, "{}i16", x), Value::I8(y) => x == y,
Value::I32(x) => write!(f, "{}i32", x), _ => false,
Value::I64(x) => write!(f, "{}i64", x), },
Value::U8(x) => write!(f, "{}u8", x), Value::I16(x) => match other {
Value::U16(x) => write!(f, "{}u16", x), Value::I16(y) => x == y,
Value::U32(x) => write!(f, "{}u32", x), _ => false,
Value::U64(x) => write!(f, "{}u64", x), },
Value::I32(x) => match other {
Value::I32(y) => x == y,
_ => false,
},
Value::I64(x) => match other {
Value::I64(y) => x == y,
_ => false,
},
Value::U8(x) => match other {
Value::U8(y) => x == y,
_ => false,
},
Value::U16(x) => match other {
Value::U16(y) => x == y,
_ => false,
},
Value::U32(x) => match other {
Value::U32(y) => x == y,
_ => false,
},
Value::U64(x) => match other {
Value::U64(y) => x == y,
_ => false,
},
Value::Function(Some(x), _) => match other {
Value::Function(Some(y), _) => x == y,
_ => false,
},
Value::Function(None, _) => false,
} }
} }
} }

View File

@@ -1,6 +1,7 @@
use crate::{ use crate::{
eval::PrimitiveType, eval::PrimitiveType,
syntax::{self, ConstantType, Location}, syntax::{self, ConstantType, Location},
util::pretty::{pretty_comma_separated, PrettySymbol},
}; };
use internment::ArcIntern; use internment::ArcIntern;
use pretty::{BoxAllocator, DocAllocator, Pretty}; use pretty::{BoxAllocator, DocAllocator, Pretty};
@@ -87,20 +88,33 @@ where
{ {
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self { match self {
TopLevel::Function(name, args, stmts, body) => allocator TopLevel::Function(name, args, stmts, expr) => {
.text("function") let base = allocator
.append(allocator.space()) .text("function")
.append(allocator.text(name.as_ref().to_string())) .append(allocator.space())
.append( .append(allocator.text(name.as_ref().to_string()))
allocator .append(allocator.space())
.intersperse( .append(
args.iter().map(|x| allocator.text(x.as_ref().to_string())), pretty_comma_separated(
", ", allocator,
&args.iter().map(PrettySymbol::from).collect(),
) )
.parens(), .parens(),
) )
.append(allocator.space()) .append(allocator.space());
.append(body.pretty(allocator)),
let mut body = allocator.nil();
for stmt in stmts {
body = body
.append(stmt.pretty(allocator))
.append(allocator.text(";"))
.append(allocator.hardline());
}
body = body.append(expr.pretty(allocator));
body = body.append(allocator.hardline());
body = body.braces();
base.append(body)
}
TopLevel::Statement(stmt) => stmt.pretty(allocator), TopLevel::Statement(stmt) => stmt.pretty(allocator),
} }
@@ -389,6 +403,14 @@ where
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self { match self {
Type::Primitive(pt) => allocator.text(format!("{}", pt)), Type::Primitive(pt) => allocator.text(format!("{}", pt)),
Type::Function(args, rettype) => {
pretty_comma_separated(allocator, &args.iter().collect())
.parens()
.append(allocator.space())
.append(allocator.text("->"))
.append(allocator.space())
.append(rettype.pretty(allocator))
}
} }
} }
} }
@@ -397,6 +419,18 @@ impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Type::Primitive(pt) => pt.fmt(f), Type::Primitive(pt) => pt.fmt(f),
Type::Function(args, ret) => {
write!(f, "(")?;
let mut argiter = args.iter().peekable();
while let Some(arg) = argiter.next() {
arg.fmt(f)?;
if argiter.peek().is_some() {
write!(f, ",")?;
}
}
write!(f, "->")?;
ret.fmt(f)
}
} }
} }
} }

View File

@@ -1,8 +1,7 @@
use super::{Primitive, Type, ValueOrRef};
use crate::eval::{EvalEnvironment, EvalError, Value}; use crate::eval::{EvalEnvironment, EvalError, Value};
use crate::ir::{Expression, Program, Statement, TopLevel}; use crate::ir::{Expression, Program, Statement, TopLevel};
use super::{Primitive, Type, ValueOrRef};
impl Program { impl Program {
/// Evaluate the program, returning either an error or a string containing everything /// Evaluate the program, returning either an error or a string containing everything
/// the program printed out. /// the program printed out.
@@ -14,7 +13,7 @@ impl Program {
for stmt in self.items.iter() { for stmt in self.items.iter() {
match stmt { match stmt {
TopLevel::Function(_, _, _) => unimplemented!(), TopLevel::Function(_, _, _, _) => unimplemented!(),
TopLevel::Statement(Statement::Binding(_, name, _, value)) => { TopLevel::Statement(Statement::Binding(_, name, _, value)) => {
let actual_value = value.eval(&env)?; let actual_value = value.eval(&env)?;
@@ -43,6 +42,7 @@ impl Expression {
match t { match t {
Type::Primitive(pt) => Ok(pt.safe_cast(&value)?), Type::Primitive(pt) => Ok(pt.safe_cast(&value)?),
Type::Function(_, _) => Err(EvalError::CastToFunction(t.to_string())),
} }
} }

View File

@@ -21,7 +21,12 @@ impl Program {
impl TopLevel { impl TopLevel {
fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) { fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) {
match self { match self {
TopLevel::Function(_, _, body) => body.register_strings(string_set), TopLevel::Function(_, _, stmts, body) => {
for stmt in stmts.iter() {
stmt.register_strings(string_set);
}
body.register_strings(string_set);
}
TopLevel::Statement(stmt) => stmt.register_strings(string_set), TopLevel::Statement(stmt) => stmt.register_strings(string_set),
} }
} }

View File

@@ -29,7 +29,7 @@ use logos::Logos;
pub mod arbitrary; pub mod arbitrary;
mod ast; mod ast;
mod eval; pub mod eval;
mod location; mod location;
mod tokens; mod tokens;
lalrpop_mod!( lalrpop_mod!(

View File

@@ -85,9 +85,9 @@ impl Location {
/// the user with some guidance. That being said, you still might want to add /// the user with some guidance. That being said, you still might want to add
/// even more information to ut, using [`Diagnostic::with_labels`], /// even more information to ut, using [`Diagnostic::with_labels`],
/// [`Diagnostic::with_notes`], or [`Diagnostic::with_code`]. /// [`Diagnostic::with_notes`], or [`Diagnostic::with_code`].
pub fn labelled_error(&self, msg: &str) -> Diagnostic<usize> { pub fn labelled_error<T: AsRef<str>>(&self, msg: T) -> Diagnostic<usize> {
Diagnostic::error().with_labels(vec![ Diagnostic::error().with_labels(vec![
Label::primary(self.file_idx, self.location.clone()).with_message(msg) Label::primary(self.file_idx, self.location.clone()).with_message(msg.as_ref())
]) ])
} }

View File

@@ -123,7 +123,7 @@ impl TopLevel {
for arg in arguments.iter() { for arg in arguments.iter() {
bound_variables.insert(arg.name.clone(), arg.location.clone()); bound_variables.insert(arg.name.clone(), arg.location.clone());
} }
let result = body.validate(&bound_variables); let result = body.validate(bound_variables);
bound_variables.release_scope(); bound_variables.release_scope();
result result
} }

View File

@@ -47,6 +47,6 @@ proptest::proptest! {
let syntax_result = input.eval(); let syntax_result = input.eval();
let ir = input.type_infer().expect("arbitrary should generate type-safe programs"); let ir = input.type_infer().expect("arbitrary should generate type-safe programs");
let ir_result = ir.eval(); let ir_result = ir.eval();
proptest::prop_assert_eq!(syntax_result, ir_result); proptest::prop_assert!(syntax_result.eq(&ir_result));
} }
} }

View File

@@ -9,6 +9,7 @@ pub use crate::ir::ast::Primitive;
use crate::{ use crate::{
eval::PrimitiveType, eval::PrimitiveType,
syntax::{self, ConstantType, Location}, syntax::{self, ConstantType, Location},
util::pretty::{pretty_comma_separated, PrettySymbol},
}; };
use internment::ArcIntern; use internment::ArcIntern;
use pretty::{DocAllocator, Pretty}; use pretty::{DocAllocator, Pretty};
@@ -87,10 +88,7 @@ where
.append( .append(
pretty_comma_separated( pretty_comma_separated(
allocator, allocator,
&args &args.iter().map(PrettySymbol::from).collect(),
.iter()
.map(|x| allocator.text(x.as_ref().to_string()))
.collect(),
) )
.parens(), .parens(),
) )
@@ -408,16 +406,3 @@ pub fn gentype() -> Type {
Type::Variable(Location::manufactured(), name) Type::Variable(Location::manufactured(), name)
} }
fn pretty_comma_separated<'a, D, A, P>(
allocator: &'a D,
args: &Vec<P>,
) -> pretty::DocBuilder<'a, D, A>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
P: Pretty<'a, D, A>,
{
let individuals = args.iter().map(|x| x.pretty(allocator));
allocator.intersperse(individuals, ", ")
}

View File

@@ -65,7 +65,7 @@ pub fn convert_top_level(
// Now let's bind these types into the environment. First, we bind our function // Now let's bind these types into the environment. First, we bind our function
// namae to the function type we just generated. // namae to the function type we just generated.
bindings.insert(funname, funtype); bindings.insert(funname.clone(), funtype);
// And then we attach the argument names to the argument types. (We have to go // And then we attach the argument names to the argument types. (We have to go
// convert all the names, first.) // convert all the names, first.)
let iargs: Vec<ArcIntern<String>> = let iargs: Vec<ArcIntern<String>> =
@@ -291,7 +291,7 @@ fn finalize_name(
renames: &mut HashMap<ArcIntern<String>, ArcIntern<String>>, renames: &mut HashMap<ArcIntern<String>, ArcIntern<String>>,
name: syntax::Name, name: syntax::Name,
) -> ArcIntern<String> { ) -> ArcIntern<String> {
if bindings.contains_key(&ArcIntern::new(name.name)) { if bindings.contains_key(&ArcIntern::new(name.name.clone())) {
let new_name = ir::gensym(&name.name); let new_name = ir::gensym(&name.name);
renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone()); renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone());
new_name new_name

View File

@@ -102,11 +102,19 @@ pub enum TypeInferenceError {
/// The user provide a constant that is too large for its inferred type. /// The user provide a constant that is too large for its inferred type.
ConstantTooLarge(Location, PrimitiveType, u64), ConstantTooLarge(Location, PrimitiveType, u64),
/// The two types needed to be equivalent, but weren't. /// The two types needed to be equivalent, but weren't.
NotEquivalent(Location, PrimitiveType, PrimitiveType), NotEquivalent(Location, Type, Type),
/// We cannot safely cast the first type to the second type. /// We cannot safely cast the first type to the second type.
CannotSafelyCast(Location, PrimitiveType, PrimitiveType), CannotSafelyCast(Location, PrimitiveType, PrimitiveType),
/// The primitive invocation provided the wrong number of arguments. /// The primitive invocation provided the wrong number of arguments.
WrongPrimitiveArity(Location, ir::Primitive, usize, usize, usize), WrongPrimitiveArity(Location, ir::Primitive, usize, usize, usize),
/// We cannot cast between function types at the moment.
CannotCastBetweenFunctinoTypes(Location, Type, Type),
/// We cannot cast from a function type to something else.
CannotCastFromFunctionType(Location, Type),
/// We cannot cast to a function type from something else.
CannotCastToFunctionType(Location, Type),
/// We cannot turn a number into a function.
CannotMakeNumberAFunction(Location, Type, Option<u64>),
/// We had a constraint we just couldn't solve. /// We had a constraint we just couldn't solve.
CouldNotSolve(Constraint), CouldNotSolve(Constraint),
} }
@@ -142,6 +150,29 @@ impl From<TypeInferenceError> for Diagnostic<usize> {
prim, prim,
observed observed
)), )),
TypeInferenceError::CannotCastBetweenFunctinoTypes(loc, t1, t2) => loc
.labelled_error("cannot cast between function types")
.with_message(format!(
"tried to cast from {} to {}",
t1, t2,
)),
TypeInferenceError::CannotCastFromFunctionType(loc, t) => loc
.labelled_error("cannot cast from a function type to anything else")
.with_message(format!(
"function type was {}", t,
)),
TypeInferenceError::CannotCastToFunctionType(loc, t) => loc
.labelled_error("cannot cast to a function type")
.with_message(format!(
"function type was {}", t,
)),
TypeInferenceError::CannotMakeNumberAFunction(loc, t, val) => loc
.labelled_error(if let Some(val) = val {
format!("cannot turn {} into a function", val)
} else {
"cannot use a constant as a function type".to_string()
})
.with_message(format!("function type was {}", t)),
TypeInferenceError::CouldNotSolve(Constraint::CanCastTo(loc, a, b)) => { TypeInferenceError::CouldNotSolve(Constraint::CanCastTo(loc, a, b)) => {
loc.labelled_error("internal error").with_message(format!( loc.labelled_error("internal error").with_message(format!(
"could not determine if it was safe to cast from {} to {:#?}", "could not determine if it was safe to cast from {} to {:#?}",
@@ -236,12 +267,12 @@ pub fn solve_constraints(
// Currently, all of our types are printable // Currently, all of our types are printable
Constraint::Printable(_loc, _ty) => changed_something = true, Constraint::Printable(_loc, _ty) => changed_something = true,
// Case #1: We have two primitive types. If they're equal, we've discharged this // Case #1a: We have two primitive types. If they're equal, we've discharged this
// constraint! We can just continue. If they're not equal, add an error and then // constraint! We can just continue. If they're not equal, add an error and then
// see what else we come up with. // see what else we come up with.
Constraint::Equivalent(loc, Type::Primitive(t1), Type::Primitive(t2)) => { Constraint::Equivalent(loc, a @ Type::Primitive(_), b @ Type::Primitive(_)) => {
if t1 != t2 { if a != b {
errors.push(TypeInferenceError::NotEquivalent(loc, t1, t2)); errors.push(TypeInferenceError::NotEquivalent(loc, a, b));
} }
changed_something = true; changed_something = true;
} }
@@ -257,7 +288,11 @@ pub fn solve_constraints(
resolutions.insert(name, t); resolutions.insert(name, t);
} }
Some(t2) if &t == t2 => {} Some(t2) if &t == t2 => {}
Some(t2) => errors.push(TypeInferenceError::NotEquivalent(loc, t, *t2)), Some(t2) => errors.push(TypeInferenceError::NotEquivalent(
loc,
Type::Primitive(t),
Type::Primitive(*t2),
)),
} }
changed_something = true; changed_something = true;
} }
@@ -284,11 +319,61 @@ pub fn solve_constraints(
changed_something = true; changed_something = true;
} }
(Some(pt1), Some(pt2)) => { (Some(pt1), Some(pt2)) => {
errors.push(TypeInferenceError::NotEquivalent(loc.clone(), *pt1, *pt2)); errors.push(TypeInferenceError::NotEquivalent(
loc.clone(),
Type::Primitive(*pt1),
Type::Primitive(*pt2),
));
changed_something = true; changed_something = true;
} }
}, },
// Case #4: Like primitives, but for function types. This is a little complicated, because
// we first want to resolve all the type variables in the two types, and then see if they're
// equivalent. Fortunately, though, we can cheat a bit. What we're going to do is first see
// if the two types have the same arity (the same number of arguments). If not, we know the
// types don't match. If they do, then we're going to just turn this into a bunch of different
// equivalence constraints by matching up each of the arguments as well as the return type, and
// then restarting the type checking loop. That will cause any of those type variables to be
// handled appropriately. This even works recursively, so we can support arbitrarily nested
// function types.
Constraint::Equivalent(
loc,
ref a @ Type::Function(ref args1, ref ret1),
ref b @ Type::Function(ref args2, ref ret2),
) => {
if args1.len() != args2.len() {
errors.push(TypeInferenceError::NotEquivalent(
loc.clone(),
a.clone(),
b.clone(),
));
} else {
for (left, right) in args1.iter().zip(args2.iter()) {
constraint_db.push(Constraint::Equivalent(
loc.clone(),
left.clone(),
right.clone(),
));
}
}
constraint_db.push(Constraint::Equivalent(
loc,
ret1.as_ref().clone(),
ret2.as_ref().clone(),
));
changed_something = true;
}
// Case #5: They're just totally the wrong types. In which case, we're done with
// this one; emit the error and drop the constraint.
Constraint::Equivalent(loc, a, b) => {
errors.push(TypeInferenceError::NotEquivalent(loc, a, b));
changed_something = true;
}
// Make sure that the provided number fits within the provided constant type. For the // Make sure that the provided number fits within the provided constant type. For the
// moment, we're going to call an error here a failure, although this could be a // moment, we're going to call an error here a failure, although this could be a
// warning in the future. // warning in the future.
@@ -320,6 +405,15 @@ pub fn solve_constraints(
} }
} }
// Function types definitely do not fit in numeric types
Constraint::FitsInNumType(loc, t @ Type::Function(_, _), val) => {
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
loc,
t.clone(),
Some(val),
));
}
// If the left type in a "can cast to" check is a variable, let's see if we can advance // If the left type in a "can cast to" check is a variable, let's see if we can advance
// it into something more tangible // it into something more tangible
Constraint::CanCastTo(loc, Type::Variable(vloc, var), to_type) => { Constraint::CanCastTo(loc, Type::Variable(vloc, var), to_type) => {
@@ -373,6 +467,36 @@ pub fn solve_constraints(
changed_something = true; changed_something = true;
} }
// If either type is a function type, then we can only cast if the two types
// are equivalent.
Constraint::CanCastTo(
loc,
t1 @ Type::Function(_, _),
t2 @ Type::Function(_, _),
) => {
if t1 != t2 {
errors.push(TypeInferenceError::CannotCastBetweenFunctinoTypes(
loc,
t1.clone(),
t2.clone(),
));
}
changed_something = true;
}
Constraint::CanCastTo(loc, t @ Type::Function(_, _), Type::Primitive(_)) => {
errors.push(TypeInferenceError::CannotCastFromFunctionType(
loc,
t.clone(),
));
changed_something = true;
}
Constraint::CanCastTo(loc, Type::Primitive(_), t @ Type::Function(_, _)) => {
errors.push(TypeInferenceError::CannotCastToFunctionType(loc, t.clone()));
changed_something = true;
}
// As per usual, if we're trying to test if a type variable is numeric, first // As per usual, if we're trying to test if a type variable is numeric, first
// we try to advance it to a primitive // we try to advance it to a primitive
Constraint::NumericType(loc, Type::Variable(vloc, var)) => { Constraint::NumericType(loc, Type::Variable(vloc, var)) => {
@@ -392,6 +516,16 @@ pub fn solve_constraints(
changed_something = true; changed_something = true;
} }
// But functions are definitely not numbers
Constraint::NumericType(loc, t @ Type::Function(_, _)) => {
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
loc,
t.clone(),
None,
));
changed_something = true;
}
// As per usual, if we're trying to test if a type variable is numeric, first // As per usual, if we're trying to test if a type variable is numeric, first
// we try to advance it to a primitive // we try to advance it to a primitive
Constraint::ConstantNumericType(loc, Type::Variable(vloc, var)) => { Constraint::ConstantNumericType(loc, Type::Variable(vloc, var)) => {
@@ -414,6 +548,16 @@ pub fn solve_constraints(
changed_something = true; changed_something = true;
} }
// But functions are definitely not numbers
Constraint::ConstantNumericType(loc, t @ Type::Function(_, _)) => {
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
loc,
t.clone(),
None,
));
changed_something = true;
}
// OK, this one could be a little tricky if we tried to do it all at once, but // OK, this one could be a little tricky if we tried to do it all at once, but
// instead what we're going to do here is just use this constraint to generate // instead what we're going to do here is just use this constraint to generate
// a bunch more constraints, and then go have the engine solve those. The only // a bunch more constraints, and then go have the engine solve those. The only

View File

@@ -1 +1,2 @@
pub mod pretty;
pub mod scoped_map; pub mod scoped_map;

47
src/util/pretty.rs Normal file
View File

@@ -0,0 +1,47 @@
use internment::ArcIntern;
use pretty::{DocAllocator, Pretty};
#[derive(Clone)]
pub struct PrettySymbol {
name: ArcIntern<String>,
}
impl<'a> From<&'a ArcIntern<String>> for PrettySymbol {
fn from(value: &'a ArcIntern<String>) -> Self {
PrettySymbol { name: value.clone() }
}
}
impl<'a, D, A> Pretty<'a, D, A> for PrettySymbol
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
allocator.text(self.name.as_ref().to_string())
}
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b PrettySymbol
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
allocator.text(self.name.as_ref().to_string())
}
}
#[allow(clippy::ptr_arg)]
pub fn pretty_comma_separated<'a, D, A, P>(
allocator: &'a D,
args: &Vec<P>,
) -> pretty::DocBuilder<'a, D, A>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
P: Pretty<'a, D, A> + Clone,
{
let individuals = args.iter().map(|x| x.clone().pretty(allocator));
allocator.intersperse(individuals, ", ")
}

View File

@@ -5,6 +5,12 @@ pub struct ScopedMap<K: Eq + Hash + PartialEq, V> {
scopes: Vec<HashMap<K, V>>, scopes: Vec<HashMap<K, V>>,
} }
impl<K: Eq + Hash + PartialEq, V> Default for ScopedMap<K, V> {
fn default() -> Self {
ScopedMap::new()
}
}
impl<K: Eq + Hash + PartialEq, V> ScopedMap<K, V> { impl<K: Eq + Hash + PartialEq, V> ScopedMap<K, V> {
/// Generate a new scoped map. /// Generate a new scoped map.
/// ///