🤔 Add a type inference engine, along with typed literals. #4
@@ -5,9 +5,9 @@ use crate::ir::Program;
|
|||||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||||
use cranelift_jit::JITModule;
|
use cranelift_jit::JITModule;
|
||||||
use cranelift_object::ObjectModule;
|
use cranelift_object::ObjectModule;
|
||||||
use std::path::Path;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use proptest::arbitrary::Arbitrary;
|
use proptest::arbitrary::Arbitrary;
|
||||||
|
use std::path::Path;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
impl Backend<JITModule> {
|
impl Backend<JITModule> {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ extern "C" fn runtime_print(
|
|||||||
Err(_) => format!("{} = {}<unknown type>", reconstituted, value),
|
Err(_) => format!("{} = {}<unknown type>", reconstituted, value),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(output_buffer) = unsafe{ output_buffer.as_mut() } {
|
if let Some(output_buffer) = unsafe { output_buffer.as_mut() } {
|
||||||
writeln!(output_buffer, "{}", output).unwrap();
|
writeln!(output_buffer, "{}", output).unwrap();
|
||||||
} else {
|
} else {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
|
|||||||
impl ConstantType {
|
impl ConstantType {
|
||||||
fn get_operators(&self) -> &'static [(&'static str, usize)] {
|
fn get_operators(&self) -> &'static [(&'static str, usize)] {
|
||||||
match self {
|
match self {
|
||||||
ConstantType::I8| ConstantType::I16 | ConstantType::I32 | ConstantType::I64 =>
|
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => {
|
||||||
&[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)],
|
&[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)]
|
||||||
ConstantType::U8| ConstantType::U16 | ConstantType::U32 | ConstantType::U64 =>
|
}
|
||||||
&[("+", 2), ("-", 2), ("*", 2), ("/", 2)],
|
ConstantType::U8 | ConstantType::U16 | ConstantType::U32 | ConstantType::U64 => {
|
||||||
|
&[("+", 2), ("-", 2), ("*", 2), ("/", 2)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,10 @@ impl Arbitrary for Program {
|
|||||||
type Strategy = BoxedStrategy<Self>;
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
|
||||||
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
|
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
|
||||||
proptest::collection::vec(ProgramStatementInfo::arbitrary(), genenv.block_length.clone())
|
proptest::collection::vec(
|
||||||
|
ProgramStatementInfo::arbitrary(),
|
||||||
|
genenv.block_length.clone(),
|
||||||
|
)
|
||||||
.prop_flat_map(move |mut items| {
|
.prop_flat_map(move |mut items| {
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
let mut genenv = genenv.clone();
|
let mut genenv = genenv.clone();
|
||||||
@@ -169,15 +173,18 @@ impl Arbitrary for Expression {
|
|||||||
// now we generate our recursive types, given our leaf strategy
|
// now we generate our recursive types, given our leaf strategy
|
||||||
leaf_strategy
|
leaf_strategy
|
||||||
.prop_recursive(3, 10, 2, move |strat| {
|
.prop_recursive(3, 10, 2, move |strat| {
|
||||||
(select(genenv.return_type.get_operators()), strat.clone(), strat).prop_map(
|
(
|
||||||
|((oper, count), left, right)| {
|
select(genenv.return_type.get_operators()),
|
||||||
|
strat.clone(),
|
||||||
|
strat,
|
||||||
|
)
|
||||||
|
.prop_map(|((oper, count), left, right)| {
|
||||||
let mut args = vec![left, right];
|
let mut args = vec![left, right];
|
||||||
while args.len() > count {
|
while args.len() > count {
|
||||||
args.pop();
|
args.pop();
|
||||||
}
|
}
|
||||||
Expression::Primitive(Location::manufactured(), oper.to_string(), args)
|
Expression::Primitive(Location::manufactured(), oper.to_string(), args)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
/// A type inference pass for NGR
|
||||||
mod ast;
|
mod ast;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod finalize;
|
mod finalize;
|
||||||
mod solve;
|
mod solve;
|
||||||
|
|
||||||
|
|
||||||
use self::convert::convert_program;
|
use self::convert::convert_program;
|
||||||
use self::finalize::finalize_program;
|
use self::finalize::finalize_program;
|
||||||
use self::solve::solve_constraints;
|
use self::solve::solve_constraints;
|
||||||
@@ -11,9 +11,9 @@ pub use self::solve::{TypeInferenceError, TypeInferenceResult, TypeInferenceWarn
|
|||||||
use crate::ir::ast as ir;
|
use crate::ir::ast as ir;
|
||||||
use crate::syntax;
|
use crate::syntax;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use proptest::prelude::Arbitrary;
|
|
||||||
#[cfg(test)]
|
|
||||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||||
|
#[cfg(test)]
|
||||||
|
use proptest::prelude::Arbitrary;
|
||||||
|
|
||||||
impl syntax::Program {
|
impl syntax::Program {
|
||||||
/// Infer the types for the syntactic AST, returning either a type-checked program in
|
/// Infer the types for the syntactic AST, returning either a type-checked program in
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use codespan_reporting::diagnostic::Diagnostic;
|
|||||||
use internment::ArcIntern;
|
use internment::ArcIntern;
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
|
/// A type inference constraint that we're going to need to solve.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Constraint {
|
pub enum Constraint {
|
||||||
/// The given type must be printable using the `print` built-in
|
/// The given type must be printable using the `print` built-in
|
||||||
@@ -48,6 +49,11 @@ impl fmt::Display for Constraint {
|
|||||||
|
|
||||||
pub type TypeResolutions = HashMap<ArcIntern<String>, PrimitiveType>;
|
pub type TypeResolutions = HashMap<ArcIntern<String>, PrimitiveType>;
|
||||||
|
|
||||||
|
/// The results of type inference; like [`Result`], but with a bit more information.
|
||||||
|
///
|
||||||
|
/// This result is parameterized, because sometimes it's handy to return slightly
|
||||||
|
/// different things; there's a [`TypeInferenceResult::map`] function for performing
|
||||||
|
/// those sorts of conversions.
|
||||||
pub enum TypeInferenceResult<Result> {
|
pub enum TypeInferenceResult<Result> {
|
||||||
Success {
|
Success {
|
||||||
result: Result,
|
result: Result,
|
||||||
@@ -62,6 +68,8 @@ pub enum TypeInferenceResult<Result> {
|
|||||||
impl<R> TypeInferenceResult<R> {
|
impl<R> TypeInferenceResult<R> {
|
||||||
// If this was a successful type inference, run the function over the result to
|
// If this was a successful type inference, run the function over the result to
|
||||||
// create a new result.
|
// create a new result.
|
||||||
|
//
|
||||||
|
// This is the moral equivalent of [`Result::map`], but for type inference results.
|
||||||
pub fn map<U, F>(self, f: F) -> TypeInferenceResult<U>
|
pub fn map<U, F>(self, f: F) -> TypeInferenceResult<U>
|
||||||
where
|
where
|
||||||
F: FnOnce(R) -> U,
|
F: FnOnce(R) -> U,
|
||||||
@@ -89,11 +97,17 @@ impl<R> TypeInferenceResult<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The various kinds of errors that can occur while doing type inference.
|
||||||
pub enum TypeInferenceError {
|
pub enum TypeInferenceError {
|
||||||
|
/// 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.
|
||||||
NotEquivalent(Location, PrimitiveType, PrimitiveType),
|
NotEquivalent(Location, PrimitiveType, PrimitiveType),
|
||||||
|
/// 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.
|
||||||
WrongPrimitiveArity(Location, ir::Primitive, usize, usize, usize),
|
WrongPrimitiveArity(Location, ir::Primitive, usize, usize, usize),
|
||||||
|
/// We had a constraint we just couldn't solve.
|
||||||
CouldNotSolve(Constraint),
|
CouldNotSolve(Constraint),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +178,10 @@ impl From<TypeInferenceError> for Diagnostic<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Warnings that we might want to tell the user about.
|
||||||
|
///
|
||||||
|
/// These are fine, probably, but could indicate some behavior the user might not
|
||||||
|
/// expect, and so they might want to do something about them.
|
||||||
pub enum TypeInferenceWarning {
|
pub enum TypeInferenceWarning {
|
||||||
DefaultedTo(Location, Type),
|
DefaultedTo(Location, Type),
|
||||||
}
|
}
|
||||||
@@ -178,6 +196,16 @@ impl From<TypeInferenceWarning> for Diagnostic<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Solve all the constraints in the provided database.
|
||||||
|
///
|
||||||
|
/// This process can take a bit, so you might not want to do it multiple times. Basically,
|
||||||
|
/// it's going to grind on these constraints until either it figures them out, or it stops
|
||||||
|
/// making progress. I haven't done the math on the constraints to even figure out if this
|
||||||
|
/// is guaranteed to halt, though, let alone terminate in some reasonable amount of time.
|
||||||
|
///
|
||||||
|
/// The return value is a type inference result, which pairs some warnings with either a
|
||||||
|
/// successful set of type resolutions (mappings from type variables to their values), or
|
||||||
|
/// a series of inference errors.
|
||||||
pub fn solve_constraints(
|
pub fn solve_constraints(
|
||||||
mut constraint_db: Vec<Constraint>,
|
mut constraint_db: Vec<Constraint>,
|
||||||
) -> TypeInferenceResult<TypeResolutions> {
|
) -> TypeInferenceResult<TypeResolutions> {
|
||||||
|
|||||||
Reference in New Issue
Block a user