checkpoint
This commit is contained in:
10
examples/basic/function0004.ngr
Normal file
10
examples/basic/function0004.ngr
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
function make_adder(x)
|
||||||
|
function (y)
|
||||||
|
x + y;
|
||||||
|
|
||||||
|
add1 = make_adder(1);
|
||||||
|
add2 = make_adder(2);
|
||||||
|
one_plus_one = add1(1);
|
||||||
|
one_plus_three = add1(3);
|
||||||
|
print one_plus_one;
|
||||||
|
print one_plus_three;
|
||||||
@@ -74,6 +74,8 @@ impl Arbitrary for Program<Type> {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TopLevel<Type> {
|
pub enum TopLevel<Type> {
|
||||||
Statement(Expression<Type>),
|
Statement(Expression<Type>),
|
||||||
|
// FIXME: Is the return type actually necessary, given we can infer it from
|
||||||
|
// the expression type?
|
||||||
Function(Variable, Vec<(Variable, Type)>, Type, Expression<Type>),
|
Function(Variable, Vec<(Variable, Type)>, Type, Expression<Type>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ pub struct Program {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum TopLevel {
|
pub enum TopLevel {
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
Function(
|
|
||||||
Option<Name>,
|
|
||||||
Vec<(Name, Option<Type>)>,
|
|
||||||
Option<Type>,
|
|
||||||
Expression,
|
|
||||||
),
|
|
||||||
Structure(Location, Name, Vec<(Name, Type)>),
|
Structure(Location, Name, Vec<(Name, Type)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +97,13 @@ pub enum Expression {
|
|||||||
Call(Location, Box<Expression>, Vec<Expression>),
|
Call(Location, Box<Expression>, Vec<Expression>),
|
||||||
Block(Location, Vec<Expression>),
|
Block(Location, Vec<Expression>),
|
||||||
Binding(Location, Name, Box<Expression>),
|
Binding(Location, Name, Box<Expression>),
|
||||||
|
Function(
|
||||||
|
Location,
|
||||||
|
Option<Name>,
|
||||||
|
Vec<(Name, Option<Type>)>,
|
||||||
|
Option<Type>,
|
||||||
|
Box<Expression>,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
@@ -157,6 +158,12 @@ impl PartialEq for Expression {
|
|||||||
Expression::Binding(_, name2, expr2) => name1 == name2 && expr1 == expr2,
|
Expression::Binding(_, name2, expr2) => name1 == name2 && expr1 == expr2,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
Expression::Function(_, mname1, args1, mret1, body1) => match other {
|
||||||
|
Expression::Function(_, mname2, args2, mret2, body2) => {
|
||||||
|
mname1 == mname2 && args1 == args2 && mret1 == mret2 && body1 == body2
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,6 +181,7 @@ impl Expression {
|
|||||||
Expression::Call(loc, _, _) => loc,
|
Expression::Call(loc, _, _) => loc,
|
||||||
Expression::Block(loc, _) => loc,
|
Expression::Block(loc, _) => loc,
|
||||||
Expression::Binding(loc, _, _) => loc,
|
Expression::Binding(loc, _, _) => loc,
|
||||||
|
Expression::Function(loc, _, _, _, _) => loc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,22 +24,6 @@ impl Program {
|
|||||||
|
|
||||||
for stmt in self.items.iter() {
|
for stmt in self.items.iter() {
|
||||||
match stmt {
|
match stmt {
|
||||||
TopLevel::Function(name, arg_names, _, body) => {
|
|
||||||
last_result = Value::Closure(
|
|
||||||
name.clone().map(Name::intern),
|
|
||||||
env.clone(),
|
|
||||||
arg_names
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|(x, _)| Name::intern(x))
|
|
||||||
.collect(),
|
|
||||||
body.clone(),
|
|
||||||
);
|
|
||||||
if let Some(name) = name {
|
|
||||||
env.insert(name.clone().intern(), last_result.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TopLevel::Expression(expr) => last_result = expr.eval(&mut stdout, &mut env)?,
|
TopLevel::Expression(expr) => last_result = expr.eval(&mut stdout, &mut env)?,
|
||||||
|
|
||||||
TopLevel::Structure(_, _, _) => {
|
TopLevel::Structure(_, _, _) => {
|
||||||
@@ -191,6 +175,25 @@ impl Expression {
|
|||||||
env.insert(name.clone().intern(), actual_value.clone());
|
env.insert(name.clone().intern(), actual_value.clone());
|
||||||
Ok(actual_value)
|
Ok(actual_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression::Function(_, name, arg_names, _, body) => {
|
||||||
|
let result = Value::Closure(
|
||||||
|
name.clone().map(Name::intern),
|
||||||
|
env.clone(),
|
||||||
|
arg_names
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|(x, _)| Name::intern(x))
|
||||||
|
.collect(),
|
||||||
|
*body.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(name) = name {
|
||||||
|
env.insert(name.clone().intern(), result.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,16 +79,10 @@ ProgramTopLevel: Vec<TopLevel> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub TopLevel: TopLevel = {
|
pub TopLevel: TopLevel = {
|
||||||
<f:Function> => f,
|
|
||||||
<s:Structure> => s,
|
<s:Structure> => s,
|
||||||
<s:Expression> ";" => TopLevel::Expression(s),
|
<s:Expression> ";" => TopLevel::Expression(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
Function: TopLevel = {
|
|
||||||
"function" <opt_name:Name?> "(" <args:Comma<Argument>> ")" <ret:("->" Type)?> <exp:Expression> ";" =>
|
|
||||||
TopLevel::Function(opt_name, args, ret.map(|x| x.1), exp),
|
|
||||||
}
|
|
||||||
|
|
||||||
Argument: (Name, Option<Type>) = {
|
Argument: (Name, Option<Type>) = {
|
||||||
<name_start: @L> <v:"<var>"> <name_end: @L> <t:(":" Type)?> =>
|
<name_start: @L> <v:"<var>"> <name_end: @L> <t:(":" Type)?> =>
|
||||||
(Name::new(v, Location::new(file_idx, name_start..name_end)), t.map(|v| v.1)),
|
(Name::new(v, Location::new(file_idx, name_start..name_end)), t.map(|v| v.1)),
|
||||||
@@ -170,6 +164,13 @@ BindingExpression: Expression = {
|
|||||||
Box::new(e),
|
Box::new(e),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
FunctionExpression,
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionExpression: Expression = {
|
||||||
|
<s:@L> "function" <opt_name:Name?> "(" <args:Comma<Argument>> ")" <ret:("->" Type)?> <exp:Expression> <e:@L> =>
|
||||||
|
Expression::Function(Location::new(file_idx, s..e), opt_name, args, ret.map(|x| x.1), Box::new(exp)),
|
||||||
|
|
||||||
PrintExpression,
|
PrintExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,47 +21,6 @@ impl TopLevel {
|
|||||||
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
|
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
|
||||||
match self {
|
match self {
|
||||||
TopLevel::Expression(expr) => expr.pretty(allocator),
|
TopLevel::Expression(expr) => expr.pretty(allocator),
|
||||||
TopLevel::Function(name, args, rettype, body) => allocator
|
|
||||||
.text("function")
|
|
||||||
.append(allocator.space())
|
|
||||||
.append(
|
|
||||||
name.as_ref()
|
|
||||||
.map(|x| allocator.text(x.to_string()))
|
|
||||||
.unwrap_or_else(|| allocator.nil()),
|
|
||||||
)
|
|
||||||
.append(
|
|
||||||
allocator
|
|
||||||
.intersperse(
|
|
||||||
args.iter().map(|(x, t)| {
|
|
||||||
allocator.text(x.to_string()).append(
|
|
||||||
t.as_ref()
|
|
||||||
.map(|t| {
|
|
||||||
allocator
|
|
||||||
.text(":")
|
|
||||||
.append(allocator.space())
|
|
||||||
.append(t.pretty(allocator))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| allocator.nil()),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
allocator.text(","),
|
|
||||||
)
|
|
||||||
.parens(),
|
|
||||||
)
|
|
||||||
.append(
|
|
||||||
rettype
|
|
||||||
.as_ref()
|
|
||||||
.map(|rettype| {
|
|
||||||
allocator
|
|
||||||
.space()
|
|
||||||
.append(allocator.text("->"))
|
|
||||||
.append(allocator.space())
|
|
||||||
.append(rettype.pretty(allocator))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| allocator.nil()),
|
|
||||||
)
|
|
||||||
.append(allocator.space())
|
|
||||||
.append(body.pretty(allocator)),
|
|
||||||
TopLevel::Structure(_, name, fields) => allocator
|
TopLevel::Structure(_, name, fields) => allocator
|
||||||
.text("struct")
|
.text("struct")
|
||||||
.append(allocator.space())
|
.append(allocator.space())
|
||||||
@@ -148,6 +107,47 @@ impl Expression {
|
|||||||
.append(allocator.text("="))
|
.append(allocator.text("="))
|
||||||
.append(allocator.space())
|
.append(allocator.space())
|
||||||
.append(expr.pretty(allocator)),
|
.append(expr.pretty(allocator)),
|
||||||
|
Expression::Function(_, name, args, rettype, body) => allocator
|
||||||
|
.text("function")
|
||||||
|
.append(allocator.space())
|
||||||
|
.append(
|
||||||
|
name.as_ref()
|
||||||
|
.map(|x| allocator.text(x.to_string()))
|
||||||
|
.unwrap_or_else(|| allocator.nil()),
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
allocator
|
||||||
|
.intersperse(
|
||||||
|
args.iter().map(|(x, t)| {
|
||||||
|
allocator.text(x.to_string()).append(
|
||||||
|
t.as_ref()
|
||||||
|
.map(|t| {
|
||||||
|
allocator
|
||||||
|
.text(":")
|
||||||
|
.append(allocator.space())
|
||||||
|
.append(t.pretty(allocator))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| allocator.nil()),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
allocator.text(","),
|
||||||
|
)
|
||||||
|
.parens(),
|
||||||
|
)
|
||||||
|
.append(
|
||||||
|
rettype
|
||||||
|
.as_ref()
|
||||||
|
.map(|rettype| {
|
||||||
|
allocator
|
||||||
|
.space()
|
||||||
|
.append(allocator.text("->"))
|
||||||
|
.append(allocator.space())
|
||||||
|
.append(rettype.pretty(allocator))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| allocator.nil()),
|
||||||
|
)
|
||||||
|
.append(allocator.space())
|
||||||
|
.append(body.pretty(allocator)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ impl Program {
|
|||||||
let mut warnings = vec![];
|
let mut warnings = vec![];
|
||||||
|
|
||||||
for stmt in self.items.iter() {
|
for stmt in self.items.iter() {
|
||||||
if let TopLevel::Function(Some(name), _, _, _) = stmt {
|
|
||||||
bound_variables.insert(name.to_string(), name.location.clone());
|
|
||||||
}
|
|
||||||
let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables);
|
let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables);
|
||||||
errors.append(&mut new_errors);
|
errors.append(&mut new_errors);
|
||||||
warnings.append(&mut new_warnings);
|
warnings.append(&mut new_warnings);
|
||||||
@@ -119,18 +116,6 @@ impl TopLevel {
|
|||||||
bound_variables: &mut ScopedMap<String, Location>,
|
bound_variables: &mut ScopedMap<String, Location>,
|
||||||
) -> (Vec<Error>, Vec<Warning>) {
|
) -> (Vec<Error>, Vec<Warning>) {
|
||||||
match self {
|
match self {
|
||||||
TopLevel::Function(name, arguments, _, body) => {
|
|
||||||
bound_variables.new_scope();
|
|
||||||
if let Some(name) = name {
|
|
||||||
bound_variables.insert(name.name.clone(), name.location.clone());
|
|
||||||
}
|
|
||||||
for (arg, _) in arguments.iter() {
|
|
||||||
bound_variables.insert(arg.name.clone(), arg.location.clone());
|
|
||||||
}
|
|
||||||
let result = body.validate(bound_variables);
|
|
||||||
bound_variables.release_scope();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
TopLevel::Expression(expr) => expr.validate(bound_variables),
|
TopLevel::Expression(expr) => expr.validate(bound_variables),
|
||||||
TopLevel::Structure(_, _, _) => (vec![], vec![]),
|
TopLevel::Structure(_, _, _) => (vec![], vec![]),
|
||||||
}
|
}
|
||||||
@@ -214,6 +199,18 @@ impl Expression {
|
|||||||
|
|
||||||
(errors, warnings)
|
(errors, warnings)
|
||||||
}
|
}
|
||||||
|
Expression::Function(_, name, arguments, _, body) => {
|
||||||
|
if let Some(name) = name {
|
||||||
|
variable_map.insert(name.name.clone(), name.location.clone());
|
||||||
|
}
|
||||||
|
variable_map.new_scope();
|
||||||
|
for (arg, _) in arguments.iter() {
|
||||||
|
variable_map.insert(arg.name.clone(), arg.location.clone());
|
||||||
|
}
|
||||||
|
let result = body.validate(variable_map);
|
||||||
|
variable_map.release_scope();
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,20 +10,43 @@
|
|||||||
//! all the constraints we've generated. If that's successful, in the final phase, we
|
//! all the constraints we've generated. If that's successful, in the final phase, we
|
||||||
//! do the final conversion to the IR AST, filling in any type information we've learned
|
//! do the final conversion to the IR AST, filling in any type information we've learned
|
||||||
//! along the way.
|
//! along the way.
|
||||||
|
mod constraint;
|
||||||
mod convert;
|
mod convert;
|
||||||
|
mod error;
|
||||||
mod finalize;
|
mod finalize;
|
||||||
|
mod result;
|
||||||
mod solve;
|
mod solve;
|
||||||
|
mod warning;
|
||||||
|
|
||||||
use self::convert::convert_program;
|
use self::constraint::Constraint;
|
||||||
use self::finalize::finalize_program;
|
use self::error::TypeInferenceError;
|
||||||
use self::solve::solve_constraints;
|
pub use self::result::TypeInferenceResult;
|
||||||
pub use self::solve::{TypeInferenceError, TypeInferenceResult, TypeInferenceWarning};
|
use self::warning::TypeInferenceWarning;
|
||||||
use crate::ir::ast as ir;
|
use crate::ir::ast as ir;
|
||||||
use crate::syntax;
|
use crate::syntax;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||||
|
use internment::ArcIntern;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use proptest::prelude::Arbitrary;
|
use proptest::prelude::Arbitrary;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InferenceEngine {
|
||||||
|
constraints: Vec<Constraint>,
|
||||||
|
type_definitions: HashMap<ArcIntern<String>, ir::TypeOrVar>,
|
||||||
|
variable_types: HashMap<ArcIntern<String>, ir::TypeOrVar>,
|
||||||
|
functions: HashMap<
|
||||||
|
ArcIntern<String>,
|
||||||
|
(
|
||||||
|
Vec<(ArcIntern<String>, ir::TypeOrVar)>,
|
||||||
|
ir::Expression<ir::TypeOrVar>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
statements: Vec<ir::Expression<ir::TypeOrVar>>,
|
||||||
|
errors: Vec<TypeInferenceError>,
|
||||||
|
warnings: Vec<TypeInferenceWarning>,
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -32,10 +55,35 @@ impl syntax::Program {
|
|||||||
/// You really should have made sure that this program was validated before running
|
/// You really should have made sure that this program was validated before running
|
||||||
/// this method, otherwise you may experience panics during operation.
|
/// this method, otherwise you may experience panics during operation.
|
||||||
pub fn type_infer(self) -> TypeInferenceResult<ir::Program<ir::Type>> {
|
pub fn type_infer(self) -> TypeInferenceResult<ir::Program<ir::Type>> {
|
||||||
let (program, constraint_db) = convert_program(self);
|
let mut engine = InferenceEngine::default();
|
||||||
let inference_result = solve_constraints(&program.type_definitions, constraint_db);
|
engine.injest_program(self);
|
||||||
|
engine.solve_constraints();
|
||||||
|
|
||||||
inference_result.map(|resolutions| finalize_program(program, &resolutions))
|
if engine.errors.is_empty() {
|
||||||
|
let resolutions = std::mem::take(&mut engine.constraints)
|
||||||
|
.into_iter()
|
||||||
|
.map(|constraint| match constraint {
|
||||||
|
Constraint::Equivalent(_, ir::TypeOrVar::Variable(_, name), result) => {
|
||||||
|
match result.try_into() {
|
||||||
|
Err(e) => panic!("Ended up with complex type {}", e),
|
||||||
|
Ok(v) => (name, v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Had something that wasn't an equivalence left at the end!"),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let warnings = std::mem::take(&mut engine.warnings);
|
||||||
|
|
||||||
|
TypeInferenceResult::Success {
|
||||||
|
result: engine.finalize_program(resolutions),
|
||||||
|
warnings,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TypeInferenceResult::Failure {
|
||||||
|
errors: engine.errors,
|
||||||
|
warnings: engine.warnings,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
79
src/type_infer/constraint.rs
Normal file
79
src/type_infer/constraint.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use crate::ir::TypeOrVar;
|
||||||
|
use crate::syntax::Location;
|
||||||
|
use internment::ArcIntern;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A type inference constraint that we're going to need to solve.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Constraint {
|
||||||
|
/// The given type must be printable using the `print` built-in
|
||||||
|
Printable(Location, TypeOrVar),
|
||||||
|
/// The provided numeric value fits in the given constant type
|
||||||
|
FitsInNumType(Location, TypeOrVar, u64),
|
||||||
|
/// The given type can be casted to the target type safely
|
||||||
|
CanCastTo(Location, TypeOrVar, TypeOrVar),
|
||||||
|
/// The given type has the given field in it, and the type of that field
|
||||||
|
/// is as given.
|
||||||
|
TypeHasField(Location, TypeOrVar, ArcIntern<String>, TypeOrVar),
|
||||||
|
/// The given type must be some numeric type, but this is not a constant
|
||||||
|
/// value, so don't try to default it if we can't figure it out
|
||||||
|
NumericType(Location, TypeOrVar),
|
||||||
|
/// The given type is attached to a constant and must be some numeric type.
|
||||||
|
/// If we can't figure it out, we should warn the user and then just use a
|
||||||
|
/// default.
|
||||||
|
ConstantNumericType(Location, TypeOrVar),
|
||||||
|
/// The two types should be equivalent
|
||||||
|
Equivalent(Location, TypeOrVar, TypeOrVar),
|
||||||
|
/// The given type can be resolved to something
|
||||||
|
IsSomething(Location, TypeOrVar),
|
||||||
|
/// The given type can be negated
|
||||||
|
IsSigned(Location, TypeOrVar),
|
||||||
|
/// Checks to see if the given named type is equivalent to the provided one.
|
||||||
|
NamedTypeIs(Location, ArcIntern<String>, TypeOrVar),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Constraint {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Constraint::Printable(_, ty) => write!(f, "PRINTABLE {}", ty),
|
||||||
|
Constraint::FitsInNumType(_, ty, num) => write!(f, "FITS_IN {} {}", num, ty),
|
||||||
|
Constraint::CanCastTo(_, ty, ty2) => write!(f, "CAST {} -> {}", ty, ty2),
|
||||||
|
Constraint::TypeHasField(_, ty1, field, ty2) => {
|
||||||
|
write!(f, "FIELD {}.{} -> {}", ty1, field, ty2)
|
||||||
|
}
|
||||||
|
Constraint::NumericType(_, ty) => write!(f, "NUMERIC {}", ty),
|
||||||
|
Constraint::ConstantNumericType(_, ty) => write!(f, "CONST_NUMERIC {}", ty),
|
||||||
|
Constraint::Equivalent(_, ty, ty2) => write!(f, "EQUIVALENT {} => {}", ty, ty2),
|
||||||
|
Constraint::IsSomething(_, ty) => write!(f, "SOMETHING {}", ty),
|
||||||
|
Constraint::IsSigned(_, ty) => write!(f, "SIGNED {}", ty),
|
||||||
|
Constraint::NamedTypeIs(_, name, ty) => write!(f, "TYPE_EQUIV {} == {}", name, ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Constraint {
|
||||||
|
/// Replace all instances of the name (anywhere! including on the left hand side of equivalences!)
|
||||||
|
/// with the given type.
|
||||||
|
///
|
||||||
|
/// Returns whether or not anything was changed in the constraint.
|
||||||
|
pub fn replace(&mut self, name: &ArcIntern<String>, replace_with: &TypeOrVar) -> bool {
|
||||||
|
match self {
|
||||||
|
Constraint::Printable(_, ty) => ty.replace(name, replace_with),
|
||||||
|
Constraint::FitsInNumType(_, ty, _) => ty.replace(name, replace_with),
|
||||||
|
Constraint::CanCastTo(_, ty1, ty2) => {
|
||||||
|
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
||||||
|
}
|
||||||
|
Constraint::TypeHasField(_, ty1, _, ty2) => {
|
||||||
|
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
||||||
|
}
|
||||||
|
Constraint::ConstantNumericType(_, ty) => ty.replace(name, replace_with),
|
||||||
|
Constraint::Equivalent(_, ty1, ty2) => {
|
||||||
|
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
||||||
|
}
|
||||||
|
Constraint::IsSigned(_, ty) => ty.replace(name, replace_with),
|
||||||
|
Constraint::IsSomething(_, ty) => ty.replace(name, replace_with),
|
||||||
|
Constraint::NumericType(_, ty) => ty.replace(name, replace_with),
|
||||||
|
Constraint::NamedTypeIs(_, name, ty) => ty.replace(name, replace_with),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,32 @@
|
|||||||
|
use super::constraint::Constraint;
|
||||||
|
use super::InferenceEngine;
|
||||||
use crate::eval::PrimitiveType;
|
use crate::eval::PrimitiveType;
|
||||||
use crate::ir;
|
use crate::ir;
|
||||||
use crate::syntax::{self, ConstantType};
|
use crate::syntax::{self, ConstantType};
|
||||||
use crate::type_infer::solve::Constraint;
|
|
||||||
use crate::util::scoped_map::ScopedMap;
|
use crate::util::scoped_map::ScopedMap;
|
||||||
use internment::ArcIntern;
|
use internment::ArcIntern;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
enum TopLevelItem {
|
struct ExpressionInfo {
|
||||||
Type(ArcIntern<String>, ir::TypeOrVar),
|
expression: ir::Expression<ir::TypeOrVar>,
|
||||||
Value(ir::TopLevel<ir::TypeOrVar>),
|
result_type: ir::TypeOrVar,
|
||||||
|
free_variables: HashSet<ArcIntern<String>>,
|
||||||
|
bound_variables: HashSet<ArcIntern<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExpressionInfo {
|
||||||
|
fn simple(expression: ir::Expression<ir::TypeOrVar>, result_type: ir::TypeOrVar) -> Self {
|
||||||
|
ExpressionInfo {
|
||||||
|
expression,
|
||||||
|
result_type,
|
||||||
|
free_variables: HashSet::new(),
|
||||||
|
bound_variables: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InferenceEngine {
|
||||||
/// This function takes a syntactic program and converts it into the IR version of the
|
/// This function takes a syntactic program and converts it into the IR version of the
|
||||||
/// program, with appropriate type variables introduced and their constraints added to
|
/// program, with appropriate type variables introduced and their constraints added to
|
||||||
/// the given database.
|
/// the given database.
|
||||||
@@ -19,144 +34,37 @@ enum TopLevelItem {
|
|||||||
/// If the input function has been validated (which it should be), then this should run
|
/// If the input function has been validated (which it should be), then this should run
|
||||||
/// into no error conditions. However, if you failed to validate the input, then this
|
/// into no error conditions. However, if you failed to validate the input, then this
|
||||||
/// function can panic.
|
/// function can panic.
|
||||||
pub fn convert_program(
|
pub fn injest_program(&mut self, program: syntax::Program) {
|
||||||
mut program: syntax::Program,
|
|
||||||
) -> (ir::Program<ir::TypeOrVar>, Vec<Constraint>) {
|
|
||||||
let mut constraint_db = Vec::new();
|
|
||||||
let mut items = Vec::new();
|
|
||||||
let mut renames = ScopedMap::new();
|
let mut renames = ScopedMap::new();
|
||||||
let mut bindings = HashMap::new();
|
|
||||||
let mut type_definitions = HashMap::new();
|
|
||||||
|
|
||||||
for item in program.items.drain(..) {
|
for item in program.items.into_iter() {
|
||||||
let tli = convert_top_level(item, &mut constraint_db, &mut renames, &mut bindings);
|
self.convert_top_level(item, &mut renames);
|
||||||
|
|
||||||
match tli {
|
|
||||||
TopLevelItem::Value(item) => items.push(item),
|
|
||||||
TopLevelItem::Type(name, decl) => {
|
|
||||||
let _ = type_definitions.insert(name, decl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
ir::Program {
|
|
||||||
items,
|
|
||||||
type_definitions,
|
|
||||||
},
|
|
||||||
constraint_db,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function takes a top-level item and converts it into the IR version of the
|
/// This function takes a top-level item and converts it into the IR version of the
|
||||||
/// program, with all the appropriate type variables introduced and their constraints
|
/// program, with all the appropriate type variables introduced and their constraints
|
||||||
/// added to the given database.
|
/// added to the given database.
|
||||||
fn convert_top_level(
|
fn convert_top_level(
|
||||||
|
&mut self,
|
||||||
top_level: syntax::TopLevel,
|
top_level: syntax::TopLevel,
|
||||||
constraint_db: &mut Vec<Constraint>,
|
|
||||||
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
||||||
bindings: &mut HashMap<ArcIntern<String>, ir::TypeOrVar>,
|
) {
|
||||||
) -> TopLevelItem {
|
|
||||||
match top_level {
|
match top_level {
|
||||||
syntax::TopLevel::Function(name, args, _, expr) => {
|
syntax::TopLevel::Expression(expr) => {
|
||||||
// First, at some point we're going to want to know a location for this function,
|
let expr_info = self.convert_expression(expr, renames);
|
||||||
// which should either be the name if we have one, or the body if we don't.
|
self.statements.push(expr_info.expression);
|
||||||
let function_location = match name {
|
|
||||||
None => expr.location().clone(),
|
|
||||||
Some(ref name) => name.location.clone(),
|
|
||||||
};
|
|
||||||
// Next, let us figure out what we're going to name this function. If the user
|
|
||||||
// didn't provide one, we'll just call it "function:<something>" for them. (We'll
|
|
||||||
// want a name for this function, eventually, so we might as well do it now.)
|
|
||||||
//
|
|
||||||
// If they did provide a name, see if we're shadowed. IF we are, then we'll have
|
|
||||||
// to specialize the name a bit. Otherwise we'll stick with their name.
|
|
||||||
let function_name = match name {
|
|
||||||
None => ir::gensym("function"),
|
|
||||||
Some(unbound) => finalize_name(bindings, renames, unbound),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function is going to have a type. We don't know what it is, but it'll have
|
|
||||||
// one.
|
|
||||||
let function_type = ir::TypeOrVar::new();
|
|
||||||
bindings.insert(function_name.clone(), function_type.clone());
|
|
||||||
|
|
||||||
// Then, let's figure out what to do with the argument names, which similarly
|
|
||||||
// may need to be renamed. We'll also generate some new type variables to associate
|
|
||||||
// with all of them.
|
|
||||||
//
|
|
||||||
// Note that we want to do all this in a new renaming scope, so that we shadow
|
|
||||||
// appropriately.
|
|
||||||
renames.new_scope();
|
|
||||||
let arginfo = args
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, mut declared_type)| {
|
|
||||||
let new_type = ir::TypeOrVar::new();
|
|
||||||
constraint_db.push(Constraint::IsSomething(
|
|
||||||
name.location.clone(),
|
|
||||||
new_type.clone(),
|
|
||||||
));
|
|
||||||
let new_name = finalize_name(bindings, renames, name.clone());
|
|
||||||
bindings.insert(new_name.clone(), new_type.clone());
|
|
||||||
|
|
||||||
if let Some(declared_type) = declared_type.take() {
|
|
||||||
let declared_type = convert_type(declared_type, constraint_db);
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
name.location.clone(),
|
|
||||||
new_type.clone(),
|
|
||||||
declared_type,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(new_name, new_type)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Now we manufacture types for the outputs and then a type for the function itself.
|
|
||||||
// We're not going to make any claims on these types, yet; they're all just unknown
|
|
||||||
// type variables we need to work out.
|
|
||||||
let rettype = ir::TypeOrVar::new();
|
|
||||||
let actual_function_type = ir::TypeOrVar::Function(
|
|
||||||
arginfo.iter().map(|x| x.1.clone()).collect(),
|
|
||||||
Box::new(rettype.clone()),
|
|
||||||
);
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
function_location,
|
|
||||||
function_type,
|
|
||||||
actual_function_type,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Now let's convert the body over to the new IR.
|
|
||||||
let (expr, ty) = convert_expression(expr, constraint_db, renames, bindings);
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
expr.location().clone(),
|
|
||||||
rettype.clone(),
|
|
||||||
ty,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Remember to exit this scoping level!
|
|
||||||
renames.release_scope();
|
|
||||||
|
|
||||||
TopLevelItem::Value(ir::TopLevel::Function(
|
|
||||||
function_name,
|
|
||||||
arginfo,
|
|
||||||
rettype,
|
|
||||||
expr,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
syntax::TopLevel::Expression(expr) => TopLevelItem::Value(ir::TopLevel::Statement(
|
|
||||||
convert_expression(expr, constraint_db, renames, bindings).0,
|
|
||||||
)),
|
|
||||||
|
|
||||||
syntax::TopLevel::Structure(_loc, name, fields) => {
|
syntax::TopLevel::Structure(_loc, name, fields) => {
|
||||||
let mut updated_fields = ir::Fields::default();
|
let mut updated_fields = ir::Fields::default();
|
||||||
|
|
||||||
for (name, field_type) in fields.into_iter() {
|
for (name, field_type) in fields.into_iter() {
|
||||||
updated_fields.insert(name.intern(), convert_type(field_type, constraint_db));
|
updated_fields.insert(name.intern(), self.convert_type(field_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
TopLevelItem::Type(name.intern(), ir::TypeOrVar::Structure(updated_fields))
|
self.type_definitions
|
||||||
|
.insert(name.intern(), ir::TypeOrVar::Structure(updated_fields));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,11 +81,10 @@ fn convert_top_level(
|
|||||||
/// you have run [`Statement::validate`], and will trigger panics in error
|
/// you have run [`Statement::validate`], and will trigger panics in error
|
||||||
/// conditions if you have run that and had it come back clean.
|
/// conditions if you have run that and had it come back clean.
|
||||||
fn convert_expression(
|
fn convert_expression(
|
||||||
|
&mut self,
|
||||||
expression: syntax::Expression,
|
expression: syntax::Expression,
|
||||||
constraint_db: &mut Vec<Constraint>,
|
|
||||||
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
||||||
bindings: &mut HashMap<ArcIntern<String>, ir::TypeOrVar>,
|
) -> ExpressionInfo {
|
||||||
) -> (ir::Expression<ir::TypeOrVar>, ir::TypeOrVar) {
|
|
||||||
match expression {
|
match expression {
|
||||||
// converting values is mostly tedious, because there's so many cases
|
// converting values is mostly tedious, because there's so many cases
|
||||||
// involved
|
// involved
|
||||||
@@ -188,7 +95,7 @@ fn convert_expression(
|
|||||||
let newtype = ir::TypeOrVar::new();
|
let newtype = ir::TypeOrVar::new();
|
||||||
let newval = ir::Value::U64(base, value);
|
let newval = ir::Value::U64(base, value);
|
||||||
|
|
||||||
constraint_db.push(Constraint::ConstantNumericType(
|
self.constraints.push(Constraint::ConstantNumericType(
|
||||||
loc.clone(),
|
loc.clone(),
|
||||||
newtype.clone(),
|
newtype.clone(),
|
||||||
));
|
));
|
||||||
@@ -232,12 +139,13 @@ fn convert_expression(
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
constraint_db.push(Constraint::FitsInNumType(
|
self.constraints.push(Constraint::FitsInNumType(
|
||||||
loc.clone(),
|
loc.clone(),
|
||||||
newtype.clone(),
|
newtype.clone(),
|
||||||
value,
|
value,
|
||||||
));
|
));
|
||||||
(
|
|
||||||
|
ExpressionInfo::simple(
|
||||||
ir::Expression::Atomic(ir::ValueOrRef::Value(loc, newtype.clone(), newval)),
|
ir::Expression::Atomic(ir::ValueOrRef::Value(loc, newtype.clone(), newval)),
|
||||||
newtype,
|
newtype,
|
||||||
)
|
)
|
||||||
@@ -248,75 +156,107 @@ fn convert_expression(
|
|||||||
let mut result_fields = HashMap::new();
|
let mut result_fields = HashMap::new();
|
||||||
let mut type_fields = ir::Fields::default();
|
let mut type_fields = ir::Fields::default();
|
||||||
let mut prereqs = vec![];
|
let mut prereqs = vec![];
|
||||||
|
let mut free_variables = HashSet::new();
|
||||||
|
let mut bound_variables = HashSet::new();
|
||||||
|
|
||||||
for (name, syntax_expr) in fields.into_iter() {
|
for (name, syntax_expr) in fields.into_iter() {
|
||||||
let (ir_expr, expr_type) =
|
let field_expr_info = self.convert_expression(syntax_expr, renames);
|
||||||
convert_expression(syntax_expr, constraint_db, renames, bindings);
|
type_fields.insert(name.clone().intern(), field_expr_info.result_type);
|
||||||
type_fields.insert(name.clone().intern(), expr_type);
|
let (prereq, value) = simplify_expr(field_expr_info.expression);
|
||||||
let (prereq, value) = simplify_expr(ir_expr);
|
|
||||||
result_fields.insert(name.clone().intern(), value);
|
result_fields.insert(name.clone().intern(), value);
|
||||||
merge_prereq(&mut prereqs, prereq);
|
merge_prereq(&mut prereqs, prereq);
|
||||||
|
free_variables.extend(field_expr_info.free_variables);
|
||||||
|
bound_variables.extend(field_expr_info.bound_variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result_type = ir::TypeOrVar::Structure(type_fields);
|
let result_type = ir::TypeOrVar::Structure(type_fields);
|
||||||
|
|
||||||
constraint_db.push(Constraint::NamedTypeIs(
|
self.constraints.push(Constraint::NamedTypeIs(
|
||||||
loc.clone(),
|
loc.clone(),
|
||||||
name.clone().intern(),
|
name.clone().intern(),
|
||||||
result_type.clone(),
|
result_type.clone(),
|
||||||
));
|
));
|
||||||
let result =
|
let expression = ir::Expression::Construct(
|
||||||
ir::Expression::Construct(loc, result_type.clone(), name.intern(), result_fields);
|
loc,
|
||||||
|
result_type.clone(),
|
||||||
|
name.intern(),
|
||||||
|
result_fields,
|
||||||
|
);
|
||||||
|
|
||||||
(finalize_expressions(prereqs, result), result_type)
|
ExpressionInfo {
|
||||||
|
expression,
|
||||||
|
result_type,
|
||||||
|
free_variables,
|
||||||
|
bound_variables,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::Reference(loc, name) => {
|
syntax::Expression::Reference(loc, name) => {
|
||||||
let iname = ArcIntern::new(name);
|
let iname = ArcIntern::new(name);
|
||||||
let final_name = renames.get(&iname).cloned().unwrap_or(iname);
|
let final_name = renames.get(&iname).cloned().unwrap_or(iname);
|
||||||
let rtype = bindings
|
let result_type = self
|
||||||
|
.variable_types
|
||||||
.get(&final_name)
|
.get(&final_name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.expect("variable bound before use");
|
.expect("variable bound before use");
|
||||||
let refexp =
|
let expression = ir::Expression::Atomic(ir::ValueOrRef::Ref(
|
||||||
ir::Expression::Atomic(ir::ValueOrRef::Ref(loc, rtype.clone(), final_name));
|
loc,
|
||||||
|
result_type.clone(),
|
||||||
|
final_name.clone(),
|
||||||
|
));
|
||||||
|
let free_variables = HashSet::from([final_name]);
|
||||||
|
|
||||||
(refexp, rtype)
|
ExpressionInfo {
|
||||||
|
expression,
|
||||||
|
result_type,
|
||||||
|
free_variables,
|
||||||
|
bound_variables: HashSet::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::FieldRef(loc, expr, field) => {
|
syntax::Expression::FieldRef(loc, expr, field) => {
|
||||||
let (nexpr, etype) = convert_expression(*expr, constraint_db, renames, bindings);
|
let mut expr_info = self.convert_expression(*expr, renames);
|
||||||
let (prereqs, val_or_ref) = simplify_expr(nexpr);
|
let (prereqs, val_or_ref) = simplify_expr(expr_info.expression);
|
||||||
let result_type = ir::TypeOrVar::new();
|
let result_type = ir::TypeOrVar::new();
|
||||||
let result = ir::Expression::FieldRef(
|
let result = ir::Expression::FieldRef(
|
||||||
loc.clone(),
|
loc.clone(),
|
||||||
result_type.clone(),
|
result_type.clone(),
|
||||||
etype.clone(),
|
expr_info.result_type.clone(),
|
||||||
val_or_ref,
|
val_or_ref,
|
||||||
field.clone().intern(),
|
field.clone().intern(),
|
||||||
);
|
);
|
||||||
|
|
||||||
constraint_db.push(Constraint::TypeHasField(
|
self.constraints.push(Constraint::TypeHasField(
|
||||||
loc,
|
loc,
|
||||||
etype,
|
expr_info.result_type.clone(),
|
||||||
field.intern(),
|
field.intern(),
|
||||||
result_type.clone(),
|
result_type.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
(finalize_expression(prereqs, result), result_type)
|
expr_info.expression = finalize_expression(prereqs, result);
|
||||||
|
expr_info.result_type = result_type;
|
||||||
|
|
||||||
|
expr_info
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::Cast(loc, target, expr) => {
|
syntax::Expression::Cast(loc, target, expr) => {
|
||||||
let (nexpr, etype) = convert_expression(*expr, constraint_db, renames, bindings);
|
let mut expr_info = self.convert_expression(*expr, renames);
|
||||||
let (prereqs, val_or_ref) = simplify_expr(nexpr);
|
let (prereqs, val_or_ref) = simplify_expr(expr_info.expression);
|
||||||
let target_type: ir::TypeOrVar = PrimitiveType::from_str(&target)
|
let target_type: ir::TypeOrVar = PrimitiveType::from_str(&target)
|
||||||
.expect("valid type for cast")
|
.expect("valid type for cast")
|
||||||
.into();
|
.into();
|
||||||
let res = ir::Expression::Cast(loc.clone(), target_type.clone(), val_or_ref);
|
let res = ir::Expression::Cast(loc.clone(), target_type.clone(), val_or_ref);
|
||||||
|
|
||||||
constraint_db.push(Constraint::CanCastTo(loc, etype, target_type.clone()));
|
self.constraints.push(Constraint::CanCastTo(
|
||||||
|
loc,
|
||||||
|
expr_info.result_type.clone(),
|
||||||
|
target_type.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
(finalize_expression(prereqs, res), target_type)
|
expr_info.expression = finalize_expression(prereqs, res);
|
||||||
|
expr_info.result_type = target_type;
|
||||||
|
|
||||||
|
expr_info
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::Primitive(loc, name) => {
|
syntax::Expression::Primitive(loc, name) => {
|
||||||
@@ -325,45 +265,50 @@ fn convert_expression(
|
|||||||
match primop {
|
match primop {
|
||||||
ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => {
|
ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => {
|
||||||
let numeric_type = ir::TypeOrVar::new_located(loc.clone());
|
let numeric_type = ir::TypeOrVar::new_located(loc.clone());
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
|
self.constraints
|
||||||
|
.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
|
||||||
let funtype = ir::TypeOrVar::Function(
|
let funtype = ir::TypeOrVar::Function(
|
||||||
vec![numeric_type.clone(), numeric_type.clone()],
|
vec![numeric_type.clone(), numeric_type.clone()],
|
||||||
Box::new(numeric_type.clone()),
|
Box::new(numeric_type.clone()),
|
||||||
);
|
);
|
||||||
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
||||||
(ir::Expression::Atomic(result_value), funtype)
|
ExpressionInfo::simple(ir::Expression::Atomic(result_value), funtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
ir::Primitive::Minus => {
|
ir::Primitive::Minus => {
|
||||||
let numeric_type = ir::TypeOrVar::new_located(loc.clone());
|
let numeric_type = ir::TypeOrVar::new_located(loc.clone());
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
|
self.constraints
|
||||||
|
.push(Constraint::NumericType(loc.clone(), numeric_type.clone()));
|
||||||
let funtype = ir::TypeOrVar::Function(
|
let funtype = ir::TypeOrVar::Function(
|
||||||
vec![numeric_type.clone(), numeric_type.clone()],
|
vec![numeric_type.clone(), numeric_type.clone()],
|
||||||
Box::new(numeric_type.clone()),
|
Box::new(numeric_type.clone()),
|
||||||
);
|
);
|
||||||
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
||||||
(ir::Expression::Atomic(result_value), funtype)
|
ExpressionInfo::simple(ir::Expression::Atomic(result_value), funtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
ir::Primitive::Print => {
|
ir::Primitive::Print => {
|
||||||
let arg_type = ir::TypeOrVar::new_located(loc.clone());
|
let arg_type = ir::TypeOrVar::new_located(loc.clone());
|
||||||
constraint_db.push(Constraint::Printable(loc.clone(), arg_type.clone()));
|
self.constraints
|
||||||
|
.push(Constraint::Printable(loc.clone(), arg_type.clone()));
|
||||||
let funtype = ir::TypeOrVar::Function(
|
let funtype = ir::TypeOrVar::Function(
|
||||||
vec![arg_type],
|
vec![arg_type],
|
||||||
Box::new(ir::TypeOrVar::Primitive(PrimitiveType::Void)),
|
Box::new(ir::TypeOrVar::Primitive(PrimitiveType::Void)),
|
||||||
);
|
);
|
||||||
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
||||||
(ir::Expression::Atomic(result_value), funtype)
|
ExpressionInfo::simple(ir::Expression::Atomic(result_value), funtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
ir::Primitive::Negate => {
|
ir::Primitive::Negate => {
|
||||||
let arg_type = ir::TypeOrVar::new_located(loc.clone());
|
let arg_type = ir::TypeOrVar::new_located(loc.clone());
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), arg_type.clone()));
|
self.constraints
|
||||||
constraint_db.push(Constraint::IsSigned(loc.clone(), arg_type.clone()));
|
.push(Constraint::NumericType(loc.clone(), arg_type.clone()));
|
||||||
|
self.constraints
|
||||||
|
.push(Constraint::IsSigned(loc.clone(), arg_type.clone()));
|
||||||
let funtype =
|
let funtype =
|
||||||
ir::TypeOrVar::Function(vec![arg_type.clone()], Box::new(arg_type));
|
ir::TypeOrVar::Function(vec![arg_type.clone()], Box::new(arg_type));
|
||||||
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
let result_value = ir::ValueOrRef::Primitive(loc, funtype.clone(), primop);
|
||||||
(ir::Expression::Atomic(result_value), funtype)
|
ExpressionInfo::simple(ir::Expression::Atomic(result_value), funtype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,79 +320,185 @@ fn convert_expression(
|
|||||||
.map(|_| ir::TypeOrVar::new())
|
.map(|_| ir::TypeOrVar::new())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (new_fun, new_fun_type) =
|
let mut expr_info = self.convert_expression(*fun, renames);
|
||||||
convert_expression(*fun, constraint_db, renames, bindings);
|
|
||||||
let target_fun_type =
|
let target_fun_type =
|
||||||
ir::TypeOrVar::Function(arg_types.clone(), Box::new(return_type.clone()));
|
ir::TypeOrVar::Function(arg_types.clone(), Box::new(return_type.clone()));
|
||||||
constraint_db.push(Constraint::Equivalent(
|
self.constraints.push(Constraint::Equivalent(
|
||||||
loc.clone(),
|
loc.clone(),
|
||||||
new_fun_type,
|
expr_info.result_type,
|
||||||
target_fun_type,
|
target_fun_type,
|
||||||
));
|
));
|
||||||
let mut prereqs = vec![];
|
let mut prereqs = vec![];
|
||||||
|
|
||||||
let (fun_prereqs, fun) = simplify_expr(new_fun);
|
let (fun_prereqs, fun) = simplify_expr(expr_info.expression);
|
||||||
merge_prereq(&mut prereqs, fun_prereqs);
|
merge_prereq(&mut prereqs, fun_prereqs);
|
||||||
|
|
||||||
let new_args = args
|
let new_args = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(arg_types)
|
.zip(arg_types)
|
||||||
.map(|(arg, target_type)| {
|
.map(|(arg, target_type)| {
|
||||||
let (new_arg, inferred_type) =
|
let arg_info = self.convert_expression(arg, renames);
|
||||||
convert_expression(arg, constraint_db, renames, bindings);
|
let location = arg_info.expression.location().clone();
|
||||||
let location = new_arg.location().clone();
|
let (arg_prereq, new_valref) = simplify_expr(arg_info.expression);
|
||||||
let (arg_prereq, new_valref) = simplify_expr(new_arg);
|
|
||||||
merge_prereq(&mut prereqs, arg_prereq);
|
merge_prereq(&mut prereqs, arg_prereq);
|
||||||
constraint_db.push(Constraint::Equivalent(
|
self.constraints.push(Constraint::Equivalent(
|
||||||
location,
|
location,
|
||||||
inferred_type,
|
arg_info.result_type,
|
||||||
target_type,
|
target_type,
|
||||||
));
|
));
|
||||||
|
expr_info.free_variables.extend(arg_info.free_variables);
|
||||||
|
expr_info.bound_variables.extend(arg_info.bound_variables);
|
||||||
new_valref
|
new_valref
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let last_call = ir::Expression::Call(loc.clone(), return_type.clone(), fun, new_args);
|
let last_call =
|
||||||
|
ir::Expression::Call(loc.clone(), return_type.clone(), fun, new_args);
|
||||||
|
|
||||||
(finalize_expressions(prereqs, last_call), return_type)
|
expr_info.expression = finalize_expressions(prereqs, last_call);
|
||||||
|
expr_info.result_type = return_type;
|
||||||
|
|
||||||
|
expr_info
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::Block(loc, stmts) => {
|
syntax::Expression::Block(loc, stmts) => {
|
||||||
let mut ret_type = ir::TypeOrVar::Primitive(PrimitiveType::Void);
|
let mut result_type = ir::TypeOrVar::Primitive(PrimitiveType::Void);
|
||||||
let mut exprs = vec![];
|
let mut exprs = vec![];
|
||||||
|
let mut free_variables = HashSet::new();
|
||||||
|
let mut bound_variables = HashSet::new();
|
||||||
|
|
||||||
for xpr in stmts.into_iter() {
|
for xpr in stmts.into_iter() {
|
||||||
let (expr, expr_type) = convert_expression(xpr, constraint_db, renames, bindings);
|
let expr_info = self.convert_expression(xpr, renames);
|
||||||
|
result_type = expr_info.result_type;
|
||||||
ret_type = expr_type;
|
exprs.push(expr_info.expression);
|
||||||
exprs.push(expr);
|
free_variables.extend(
|
||||||
|
expr_info
|
||||||
|
.free_variables
|
||||||
|
.difference(&bound_variables)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
bound_variables.extend(expr_info.bound_variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
ExpressionInfo {
|
||||||
ir::Expression::Block(loc, ret_type.clone(), exprs),
|
expression: ir::Expression::Block(loc, result_type.clone(), exprs),
|
||||||
ret_type,
|
result_type,
|
||||||
)
|
free_variables,
|
||||||
|
bound_variables,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syntax::Expression::Binding(loc, name, expr) => {
|
syntax::Expression::Binding(loc, name, expr) => {
|
||||||
let (expr, ty) = convert_expression(*expr, constraint_db, renames, bindings);
|
let mut expr_info = self.convert_expression(*expr, renames);
|
||||||
let final_name = finalize_name(bindings, renames, name);
|
let final_name = self.finalize_name(renames, name);
|
||||||
bindings.insert(final_name.clone(), ty.clone());
|
self.variable_types
|
||||||
|
.insert(final_name.clone(), expr_info.result_type.clone());
|
||||||
|
expr_info.expression = ir::Expression::Bind(
|
||||||
|
loc,
|
||||||
|
final_name.clone(),
|
||||||
|
expr_info.result_type.clone(),
|
||||||
|
Box::new(expr_info.expression),
|
||||||
|
);
|
||||||
|
expr_info.bound_variables.insert(final_name);
|
||||||
|
expr_info
|
||||||
|
}
|
||||||
|
|
||||||
(
|
syntax::Expression::Function(_, name, args, _, expr) => {
|
||||||
ir::Expression::Bind(loc, final_name, ty.clone(), Box::new(expr)),
|
// First, at some point we're going to want to know a location for this function,
|
||||||
ty,
|
// which should either be the name if we have one, or the body if we don't.
|
||||||
)
|
let function_location = match name {
|
||||||
|
None => expr.location().clone(),
|
||||||
|
Some(ref name) => name.location.clone(),
|
||||||
|
};
|
||||||
|
// Next, let us figure out what we're going to name this function. If the user
|
||||||
|
// didn't provide one, we'll just call it "function:<something>" for them. (We'll
|
||||||
|
// want a name for this function, eventually, so we might as well do it now.)
|
||||||
|
//
|
||||||
|
// If they did provide a name, see if we're shadowed. IF we are, then we'll have
|
||||||
|
// to specialize the name a bit. Otherwise we'll stick with their name.
|
||||||
|
let function_name = match name {
|
||||||
|
None => ir::gensym("function"),
|
||||||
|
Some(unbound) => self.finalize_name(renames, unbound),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function is going to have a type. We don't know what it is, but it'll have
|
||||||
|
// one.
|
||||||
|
let function_type = ir::TypeOrVar::new();
|
||||||
|
self.variable_types
|
||||||
|
.insert(function_name.clone(), function_type.clone());
|
||||||
|
|
||||||
|
// Then, let's figure out what to do with the argument names, which similarly
|
||||||
|
// may need to be renamed. We'll also generate some new type variables to associate
|
||||||
|
// with all of them.
|
||||||
|
//
|
||||||
|
// Note that we want to do all this in a new renaming scope, so that we shadow
|
||||||
|
// appropriately.
|
||||||
|
renames.new_scope();
|
||||||
|
let arginfo = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, mut declared_type)| {
|
||||||
|
let new_type = ir::TypeOrVar::new();
|
||||||
|
self.constraints.push(Constraint::IsSomething(
|
||||||
|
name.location.clone(),
|
||||||
|
new_type.clone(),
|
||||||
|
));
|
||||||
|
let new_name = self.finalize_name(renames, name.clone());
|
||||||
|
self.variable_types
|
||||||
|
.insert(new_name.clone(), new_type.clone());
|
||||||
|
|
||||||
|
if let Some(declared_type) = declared_type.take() {
|
||||||
|
let declared_type = self.convert_type(declared_type);
|
||||||
|
self.constraints.push(Constraint::Equivalent(
|
||||||
|
name.location.clone(),
|
||||||
|
new_type.clone(),
|
||||||
|
declared_type,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
(new_name, new_type)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Now we manufacture types for the outputs and then a type for the function itself.
|
||||||
|
// We're not going to make any claims on these types, yet; they're all just unknown
|
||||||
|
// type variables we need to work out.
|
||||||
|
let rettype = ir::TypeOrVar::new();
|
||||||
|
let actual_function_type = ir::TypeOrVar::Function(
|
||||||
|
arginfo.iter().map(|x| x.1.clone()).collect(),
|
||||||
|
Box::new(rettype.clone()),
|
||||||
|
);
|
||||||
|
self.constraints.push(Constraint::Equivalent(
|
||||||
|
function_location,
|
||||||
|
function_type,
|
||||||
|
actual_function_type,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Now let's convert the body over to the new IR.
|
||||||
|
let expr_info = self.convert_expression(*expr, renames);
|
||||||
|
self.constraints.push(Constraint::Equivalent(
|
||||||
|
expr_info.expression.location().clone(),
|
||||||
|
rettype.clone(),
|
||||||
|
expr_info.result_type.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Remember to exit this scoping level!
|
||||||
|
renames.release_scope();
|
||||||
|
|
||||||
|
self.functions
|
||||||
|
.insert(function_name, (arginfo, expr_info.expression.clone()));
|
||||||
|
|
||||||
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_type(ty: syntax::Type, constraint_db: &mut Vec<Constraint>) -> ir::TypeOrVar {
|
fn convert_type(&mut self, ty: syntax::Type) -> ir::TypeOrVar {
|
||||||
match ty {
|
match ty {
|
||||||
syntax::Type::Named(x) => match PrimitiveType::from_str(x.name.as_str()) {
|
syntax::Type::Named(x) => match PrimitiveType::from_str(x.name.as_str()) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let retval = ir::TypeOrVar::new_located(x.location.clone());
|
let retval = ir::TypeOrVar::new_located(x.location.clone());
|
||||||
constraint_db.push(Constraint::NamedTypeIs(
|
self.constraints.push(Constraint::NamedTypeIs(
|
||||||
x.location.clone(),
|
x.location.clone(),
|
||||||
x.intern(),
|
x.intern(),
|
||||||
retval.clone(),
|
retval.clone(),
|
||||||
@@ -461,7 +512,7 @@ fn convert_type(ty: syntax::Type, constraint_db: &mut Vec<Constraint>) -> ir::Ty
|
|||||||
|
|
||||||
for (name, field_type) in fields.into_iter() {
|
for (name, field_type) in fields.into_iter() {
|
||||||
let new_field_type = field_type
|
let new_field_type = field_type
|
||||||
.map(|x| convert_type(x, constraint_db))
|
.map(|x| self.convert_type(x))
|
||||||
.unwrap_or_else(ir::TypeOrVar::new);
|
.unwrap_or_else(ir::TypeOrVar::new);
|
||||||
new_fields.insert(name.intern(), new_field_type);
|
new_fields.insert(name.intern(), new_field_type);
|
||||||
}
|
}
|
||||||
@@ -471,6 +522,24 @@ fn convert_type(ty: syntax::Type, constraint_db: &mut Vec<Constraint>) -> ir::Ty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize_name(
|
||||||
|
&mut self,
|
||||||
|
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
||||||
|
name: syntax::Name,
|
||||||
|
) -> ArcIntern<String> {
|
||||||
|
if self
|
||||||
|
.variable_types
|
||||||
|
.contains_key(&ArcIntern::new(name.name.clone()))
|
||||||
|
{
|
||||||
|
let new_name = ir::gensym(&name.name);
|
||||||
|
renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone());
|
||||||
|
new_name
|
||||||
|
} else {
|
||||||
|
ArcIntern::new(name.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn simplify_expr(
|
fn simplify_expr(
|
||||||
expr: ir::Expression<ir::TypeOrVar>,
|
expr: ir::Expression<ir::TypeOrVar>,
|
||||||
) -> (
|
) -> (
|
||||||
@@ -520,20 +589,6 @@ fn finalize_expressions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_name(
|
|
||||||
bindings: &HashMap<ArcIntern<String>, ir::TypeOrVar>,
|
|
||||||
renames: &mut ScopedMap<ArcIntern<String>, ArcIntern<String>>,
|
|
||||||
name: syntax::Name,
|
|
||||||
) -> ArcIntern<String> {
|
|
||||||
if bindings.contains_key(&ArcIntern::new(name.name.clone())) {
|
|
||||||
let new_name = ir::gensym(&name.name);
|
|
||||||
renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone());
|
|
||||||
new_name
|
|
||||||
} else {
|
|
||||||
ArcIntern::new(name.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_prereq<T>(left: &mut Vec<T>, prereq: Option<T>) {
|
fn merge_prereq<T>(left: &mut Vec<T>, prereq: Option<T>) {
|
||||||
if let Some(item) = prereq {
|
if let Some(item) = prereq {
|
||||||
left.push(item)
|
left.push(item)
|
||||||
|
|||||||
146
src/type_infer/error.rs
Normal file
146
src/type_infer/error.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use super::constraint::Constraint;
|
||||||
|
use crate::eval::PrimitiveType;
|
||||||
|
use crate::ir::{Primitive, TypeOrVar};
|
||||||
|
use crate::syntax::Location;
|
||||||
|
use codespan_reporting::diagnostic::Diagnostic;
|
||||||
|
use internment::ArcIntern;
|
||||||
|
|
||||||
|
/// The various kinds of errors that can occur while doing type inference.
|
||||||
|
pub enum TypeInferenceError {
|
||||||
|
/// The user provide a constant that is too large for its inferred type.
|
||||||
|
ConstantTooLarge(Location, PrimitiveType, u64),
|
||||||
|
/// Somehow we're trying to use a non-number as a number
|
||||||
|
NotANumber(Location, PrimitiveType),
|
||||||
|
/// The two types needed to be equivalent, but weren't.
|
||||||
|
NotEquivalent(Location, TypeOrVar, TypeOrVar),
|
||||||
|
/// We cannot safely cast the first type to the second type.
|
||||||
|
CannotSafelyCast(Location, PrimitiveType, PrimitiveType),
|
||||||
|
/// The primitive invocation provided the wrong number of arguments.
|
||||||
|
WrongPrimitiveArity(Location, Primitive, usize, usize, usize),
|
||||||
|
/// We cannot cast between the type types, for any number of reasons
|
||||||
|
CannotCast(Location, TypeOrVar, TypeOrVar),
|
||||||
|
/// We cannot turn a number into a function.
|
||||||
|
CannotMakeNumberAFunction(Location, TypeOrVar, Option<u64>),
|
||||||
|
/// We cannot turn a number into a Structure.
|
||||||
|
CannotMakeNumberAStructure(Location, TypeOrVar, Option<u64>),
|
||||||
|
/// We had a constraint we just couldn't solve.
|
||||||
|
CouldNotSolve(Constraint),
|
||||||
|
/// Functions are not printable.
|
||||||
|
FunctionsAreNotPrintable(Location),
|
||||||
|
/// The given type isn't signed, and can't be negated
|
||||||
|
IsNotSigned(Location, TypeOrVar),
|
||||||
|
/// The given type doesn't have the given field.
|
||||||
|
NoFieldForType(Location, ArcIntern<String>, TypeOrVar),
|
||||||
|
/// There is no type with the given name.
|
||||||
|
UnknownTypeName(Location, ArcIntern<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypeInferenceError> for Diagnostic<usize> {
|
||||||
|
fn from(value: TypeInferenceError) -> Self {
|
||||||
|
match value {
|
||||||
|
TypeInferenceError::ConstantTooLarge(loc, primty, value) => loc
|
||||||
|
.labelled_error("constant too large for type")
|
||||||
|
.with_message(format!(
|
||||||
|
"Type {} has a max value of {}, which is smaller than {}",
|
||||||
|
primty,
|
||||||
|
primty.max_value().expect("constant type has max value"),
|
||||||
|
value
|
||||||
|
)),
|
||||||
|
TypeInferenceError::NotANumber(loc, primty) => loc
|
||||||
|
.labelled_error("not a numeric type")
|
||||||
|
.with_message(format!(
|
||||||
|
"For some reason, we're trying to use {} as a numeric type",
|
||||||
|
primty,
|
||||||
|
)),
|
||||||
|
TypeInferenceError::NotEquivalent(loc, ty1, ty2) => loc
|
||||||
|
.labelled_error("type inference error")
|
||||||
|
.with_message(format!("Expected type {}, received type {}", ty1, ty2)),
|
||||||
|
TypeInferenceError::CannotSafelyCast(loc, ty1, ty2) => loc
|
||||||
|
.labelled_error("unsafe type cast")
|
||||||
|
.with_message(format!("Cannot safely cast {} to {}", ty1, ty2)),
|
||||||
|
TypeInferenceError::WrongPrimitiveArity(loc, prim, lower, upper, observed) => loc
|
||||||
|
.labelled_error("wrong number of arguments")
|
||||||
|
.with_message(format!(
|
||||||
|
"expected {} for {}, received {}",
|
||||||
|
if lower == upper && lower > 1 {
|
||||||
|
format!("{} arguments", lower)
|
||||||
|
} else if lower == upper {
|
||||||
|
format!("{} argument", lower)
|
||||||
|
} else {
|
||||||
|
format!("{}-{} arguments", lower, upper)
|
||||||
|
},
|
||||||
|
prim,
|
||||||
|
observed
|
||||||
|
)),
|
||||||
|
TypeInferenceError::CannotCast(loc, t1, t2) => loc
|
||||||
|
.labelled_error("cannot cast between types")
|
||||||
|
.with_message(format!(
|
||||||
|
"tried to cast from {} to {}",
|
||||||
|
t1, t2,
|
||||||
|
)),
|
||||||
|
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::CannotMakeNumberAStructure(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::FunctionsAreNotPrintable(loc) => loc
|
||||||
|
.labelled_error("cannot print function values"),
|
||||||
|
TypeInferenceError::IsNotSigned(loc, pt) => loc
|
||||||
|
.labelled_error(format!("type {} is not signed", pt))
|
||||||
|
.with_message("and so it cannot be negated"),
|
||||||
|
TypeInferenceError::NoFieldForType(loc, field, t) => loc
|
||||||
|
.labelled_error(format!("no field {} available for type {}", field, t)),
|
||||||
|
TypeInferenceError::UnknownTypeName(loc , name) => loc
|
||||||
|
.labelled_error(format!("unknown type named {}", name)),
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::CanCastTo(loc, a, b)) => {
|
||||||
|
loc.labelled_error("internal error").with_message(format!(
|
||||||
|
"could not determine if it was safe to cast from {} to {}",
|
||||||
|
a, b
|
||||||
|
))
|
||||||
|
}
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::TypeHasField(loc, a, field, _)) => {
|
||||||
|
loc.labelled_error("internal error")
|
||||||
|
.with_message(format!("fould not determine if type {} has field {}", a, field))
|
||||||
|
}
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::Equivalent(loc, a, b)) => {
|
||||||
|
loc.labelled_error("internal error").with_message(format!(
|
||||||
|
"could not determine if {} and {} were equivalent",
|
||||||
|
a, b
|
||||||
|
))
|
||||||
|
}
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::FitsInNumType(loc, ty, val)) => {
|
||||||
|
loc.labelled_error("internal error").with_message(format!(
|
||||||
|
"Could not determine if {} could fit in {}",
|
||||||
|
val, ty
|
||||||
|
))
|
||||||
|
}
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::NumericType(loc, ty)) => loc
|
||||||
|
.labelled_error("internal error")
|
||||||
|
.with_message(format!("Could not determine if {} was a numeric type", ty)),
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::ConstantNumericType(loc, ty)) =>
|
||||||
|
panic!("What? Constants should always eventually be solved, even by default; {:?} and type {:?}", loc, ty),
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::Printable(loc, ty)) => loc
|
||||||
|
.labelled_error("internal error")
|
||||||
|
.with_message(format!("Could not determine if type {} was printable", ty)),
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::IsSomething(loc, _)) => {
|
||||||
|
loc.labelled_error("could not infer type")
|
||||||
|
.with_message("Could not find *any* type information; is this an unused function argument?")
|
||||||
|
}
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::IsSigned(loc, t)) => loc
|
||||||
|
.labelled_error("internal error")
|
||||||
|
.with_message(format!("could not infer that type {} was signed", t)),
|
||||||
|
TypeInferenceError::CouldNotSolve(Constraint::NamedTypeIs(loc, name, ty)) => loc
|
||||||
|
.labelled_error("internal error")
|
||||||
|
.with_message(format!("could not infer that the name {} refers to {}", name, ty)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +1,61 @@
|
|||||||
use super::solve::TypeResolutions;
|
|
||||||
use crate::eval::PrimitiveType;
|
use crate::eval::PrimitiveType;
|
||||||
use crate::ir::{Expression, Program, TopLevel, Type, TypeOrVar, Value, ValueOrRef};
|
use crate::ir::{Expression, Program, TopLevel, Type, TypeOrVar, TypeWithVoid, Value, ValueOrRef};
|
||||||
|
use crate::syntax::Location;
|
||||||
|
use internment::ArcIntern;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn finalize_program(
|
pub type TypeResolutions = HashMap<ArcIntern<String>, Type>;
|
||||||
program: Program<TypeOrVar>,
|
|
||||||
resolutions: &TypeResolutions,
|
impl super::InferenceEngine {
|
||||||
) -> Program<Type> {
|
pub fn finalize_program(self, resolutions: TypeResolutions) -> Program<Type> {
|
||||||
for (name, ty) in resolutions.iter() {
|
for (name, ty) in resolutions.iter() {
|
||||||
tracing::debug!(name = %name, resolved_type = %ty, "resolved type variable");
|
tracing::debug!(name = %name, resolved_type = %ty, "resolved type variable");
|
||||||
}
|
}
|
||||||
|
|
||||||
Program {
|
let mut type_definitions = HashMap::new();
|
||||||
items: program
|
let mut items = Vec::new();
|
||||||
.items
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| finalize_top_level(x, resolutions))
|
|
||||||
.collect(),
|
|
||||||
|
|
||||||
type_definitions: program
|
for (name, def) in self.type_definitions.into_iter() {
|
||||||
.type_definitions
|
type_definitions.insert(name, finalize_type(def, &resolutions));
|
||||||
.into_iter()
|
|
||||||
.map(|(n, t)| (n, finalize_type(t, resolutions)))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_top_level(item: TopLevel<TypeOrVar>, resolutions: &TypeResolutions) -> TopLevel<Type> {
|
for (name, (arguments, body)) in self.functions.into_iter() {
|
||||||
match item {
|
let new_body = finalize_expression(body, &resolutions);
|
||||||
TopLevel::Function(name, args, rettype, expr) => TopLevel::Function(
|
let arguments = arguments
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, t)| (name, finalize_type(t, &resolutions)))
|
||||||
|
.collect();
|
||||||
|
items.push(TopLevel::Function(
|
||||||
name,
|
name,
|
||||||
args.into_iter()
|
arguments,
|
||||||
.map(|(name, t)| (name, finalize_type(t, resolutions)))
|
new_body.type_of(),
|
||||||
.collect(),
|
new_body,
|
||||||
finalize_type(rettype, resolutions),
|
));
|
||||||
finalize_expression(expr, resolutions),
|
}
|
||||||
),
|
|
||||||
TopLevel::Statement(expr) => TopLevel::Statement(finalize_expression(expr, resolutions)),
|
let mut body = vec![];
|
||||||
|
let mut last_type = Type::void();
|
||||||
|
let mut location = None;
|
||||||
|
|
||||||
|
for expr in self.statements.into_iter() {
|
||||||
|
let next = finalize_expression(expr, &resolutions);
|
||||||
|
location = location
|
||||||
|
.map(|x: Location| x.merge(next.location()))
|
||||||
|
.unwrap_or_else(|| Some(next.location().clone()));
|
||||||
|
last_type = next.type_of();
|
||||||
|
body.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(TopLevel::Statement(Expression::Block(
|
||||||
|
location.unwrap_or_else(Location::manufactured),
|
||||||
|
last_type,
|
||||||
|
body,
|
||||||
|
)));
|
||||||
|
|
||||||
|
Program {
|
||||||
|
items,
|
||||||
|
type_definitions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
50
src/type_infer/result.rs
Normal file
50
src/type_infer/result.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use super::error::TypeInferenceError;
|
||||||
|
use super::warning::TypeInferenceWarning;
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
Success {
|
||||||
|
result: Result,
|
||||||
|
warnings: Vec<TypeInferenceWarning>,
|
||||||
|
},
|
||||||
|
Failure {
|
||||||
|
errors: Vec<TypeInferenceError>,
|
||||||
|
warnings: Vec<TypeInferenceWarning>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> TypeInferenceResult<R> {
|
||||||
|
// If this was a successful type inference, run the function over the result to
|
||||||
|
// 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>
|
||||||
|
where
|
||||||
|
F: FnOnce(R) -> U,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
TypeInferenceResult::Success { result, warnings } => TypeInferenceResult::Success {
|
||||||
|
result: f(result),
|
||||||
|
warnings,
|
||||||
|
},
|
||||||
|
|
||||||
|
TypeInferenceResult::Failure { errors, warnings } => {
|
||||||
|
TypeInferenceResult::Failure { errors, warnings }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the final result, or panic if it's not a success
|
||||||
|
pub fn expect(self, msg: &str) -> R {
|
||||||
|
match self {
|
||||||
|
TypeInferenceResult::Success { result, .. } => result,
|
||||||
|
TypeInferenceResult::Failure { .. } => {
|
||||||
|
panic!("tried to get value from failed type inference: {}", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,293 +1,11 @@
|
|||||||
|
use super::constraint::Constraint;
|
||||||
|
use super::error::TypeInferenceError;
|
||||||
|
use super::warning::TypeInferenceWarning;
|
||||||
use crate::eval::PrimitiveType;
|
use crate::eval::PrimitiveType;
|
||||||
use crate::ir::{Primitive, Type, TypeOrVar};
|
use crate::ir::TypeOrVar;
|
||||||
use crate::syntax::Location;
|
|
||||||
use codespan_reporting::diagnostic::Diagnostic;
|
|
||||||
use internment::ArcIntern;
|
use internment::ArcIntern;
|
||||||
use std::{collections::HashMap, fmt};
|
|
||||||
|
|
||||||
/// A type inference constraint that we're going to need to solve.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Constraint {
|
|
||||||
/// The given type must be printable using the `print` built-in
|
|
||||||
Printable(Location, TypeOrVar),
|
|
||||||
/// The provided numeric value fits in the given constant type
|
|
||||||
FitsInNumType(Location, TypeOrVar, u64),
|
|
||||||
/// The given type can be casted to the target type safely
|
|
||||||
CanCastTo(Location, TypeOrVar, TypeOrVar),
|
|
||||||
/// The given type has the given field in it, and the type of that field
|
|
||||||
/// is as given.
|
|
||||||
TypeHasField(Location, TypeOrVar, ArcIntern<String>, TypeOrVar),
|
|
||||||
/// The given type must be some numeric type, but this is not a constant
|
|
||||||
/// value, so don't try to default it if we can't figure it out
|
|
||||||
NumericType(Location, TypeOrVar),
|
|
||||||
/// The given type is attached to a constant and must be some numeric type.
|
|
||||||
/// If we can't figure it out, we should warn the user and then just use a
|
|
||||||
/// default.
|
|
||||||
ConstantNumericType(Location, TypeOrVar),
|
|
||||||
/// The two types should be equivalent
|
|
||||||
Equivalent(Location, TypeOrVar, TypeOrVar),
|
|
||||||
/// The given type can be resolved to something
|
|
||||||
IsSomething(Location, TypeOrVar),
|
|
||||||
/// The given type can be negated
|
|
||||||
IsSigned(Location, TypeOrVar),
|
|
||||||
/// Checks to see if the given named type is equivalent to the provided one.
|
|
||||||
NamedTypeIs(Location, ArcIntern<String>, TypeOrVar),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Constraint {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Constraint::Printable(_, ty) => write!(f, "PRINTABLE {}", ty),
|
|
||||||
Constraint::FitsInNumType(_, ty, num) => write!(f, "FITS_IN {} {}", num, ty),
|
|
||||||
Constraint::CanCastTo(_, ty, ty2) => write!(f, "CAST {} -> {}", ty, ty2),
|
|
||||||
Constraint::TypeHasField(_, ty1, field, ty2) => {
|
|
||||||
write!(f, "FIELD {}.{} -> {}", ty1, field, ty2)
|
|
||||||
}
|
|
||||||
Constraint::NumericType(_, ty) => write!(f, "NUMERIC {}", ty),
|
|
||||||
Constraint::ConstantNumericType(_, ty) => write!(f, "CONST_NUMERIC {}", ty),
|
|
||||||
Constraint::Equivalent(_, ty, ty2) => write!(f, "EQUIVALENT {} => {}", ty, ty2),
|
|
||||||
Constraint::IsSomething(_, ty) => write!(f, "SOMETHING {}", ty),
|
|
||||||
Constraint::IsSigned(_, ty) => write!(f, "SIGNED {}", ty),
|
|
||||||
Constraint::NamedTypeIs(_, name, ty) => write!(f, "TYPE_EQUIV {} == {}", name, ty),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Constraint {
|
|
||||||
/// Replace all instances of the name (anywhere! including on the left hand side of equivalences!)
|
|
||||||
/// with the given type.
|
|
||||||
///
|
|
||||||
/// Returns whether or not anything was changed in the constraint.
|
|
||||||
fn replace(&mut self, name: &ArcIntern<String>, replace_with: &TypeOrVar) -> bool {
|
|
||||||
match self {
|
|
||||||
Constraint::Printable(_, ty) => ty.replace(name, replace_with),
|
|
||||||
Constraint::FitsInNumType(_, ty, _) => ty.replace(name, replace_with),
|
|
||||||
Constraint::CanCastTo(_, ty1, ty2) => {
|
|
||||||
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
|
||||||
}
|
|
||||||
Constraint::TypeHasField(_, ty1, _, ty2) => {
|
|
||||||
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
|
||||||
}
|
|
||||||
Constraint::ConstantNumericType(_, ty) => ty.replace(name, replace_with),
|
|
||||||
Constraint::Equivalent(_, ty1, ty2) => {
|
|
||||||
ty1.replace(name, replace_with) || ty2.replace(name, replace_with)
|
|
||||||
}
|
|
||||||
Constraint::IsSigned(_, ty) => ty.replace(name, replace_with),
|
|
||||||
Constraint::IsSomething(_, ty) => ty.replace(name, replace_with),
|
|
||||||
Constraint::NumericType(_, ty) => ty.replace(name, replace_with),
|
|
||||||
Constraint::NamedTypeIs(_, name, ty) => ty.replace(name, replace_with),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TypeResolutions = HashMap<ArcIntern<String>, Type>;
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
Success {
|
|
||||||
result: Result,
|
|
||||||
warnings: Vec<TypeInferenceWarning>,
|
|
||||||
},
|
|
||||||
Failure {
|
|
||||||
errors: Vec<TypeInferenceError>,
|
|
||||||
warnings: Vec<TypeInferenceWarning>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R> TypeInferenceResult<R> {
|
|
||||||
// If this was a successful type inference, run the function over the result to
|
|
||||||
// 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>
|
|
||||||
where
|
|
||||||
F: FnOnce(R) -> U,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
TypeInferenceResult::Success { result, warnings } => TypeInferenceResult::Success {
|
|
||||||
result: f(result),
|
|
||||||
warnings,
|
|
||||||
},
|
|
||||||
|
|
||||||
TypeInferenceResult::Failure { errors, warnings } => {
|
|
||||||
TypeInferenceResult::Failure { errors, warnings }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the final result, or panic if it's not a success
|
|
||||||
pub fn expect(self, msg: &str) -> R {
|
|
||||||
match self {
|
|
||||||
TypeInferenceResult::Success { result, .. } => result,
|
|
||||||
TypeInferenceResult::Failure { .. } => {
|
|
||||||
panic!("tried to get value from failed type inference: {}", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The various kinds of errors that can occur while doing type inference.
|
|
||||||
pub enum TypeInferenceError {
|
|
||||||
/// The user provide a constant that is too large for its inferred type.
|
|
||||||
ConstantTooLarge(Location, PrimitiveType, u64),
|
|
||||||
/// Somehow we're trying to use a non-number as a number
|
|
||||||
NotANumber(Location, PrimitiveType),
|
|
||||||
/// The two types needed to be equivalent, but weren't.
|
|
||||||
NotEquivalent(Location, TypeOrVar, TypeOrVar),
|
|
||||||
/// We cannot safely cast the first type to the second type.
|
|
||||||
CannotSafelyCast(Location, PrimitiveType, PrimitiveType),
|
|
||||||
/// The primitive invocation provided the wrong number of arguments.
|
|
||||||
WrongPrimitiveArity(Location, Primitive, usize, usize, usize),
|
|
||||||
/// We cannot cast between the type types, for any number of reasons
|
|
||||||
CannotCast(Location, TypeOrVar, TypeOrVar),
|
|
||||||
/// We cannot turn a number into a function.
|
|
||||||
CannotMakeNumberAFunction(Location, TypeOrVar, Option<u64>),
|
|
||||||
/// We cannot turn a number into a Structure.
|
|
||||||
CannotMakeNumberAStructure(Location, TypeOrVar, Option<u64>),
|
|
||||||
/// We had a constraint we just couldn't solve.
|
|
||||||
CouldNotSolve(Constraint),
|
|
||||||
/// Functions are not printable.
|
|
||||||
FunctionsAreNotPrintable(Location),
|
|
||||||
/// The given type isn't signed, and can't be negated
|
|
||||||
IsNotSigned(Location, TypeOrVar),
|
|
||||||
/// The given type doesn't have the given field.
|
|
||||||
NoFieldForType(Location, ArcIntern<String>, TypeOrVar),
|
|
||||||
/// There is no type with the given name.
|
|
||||||
UnknownTypeName(Location, ArcIntern<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TypeInferenceError> for Diagnostic<usize> {
|
|
||||||
fn from(value: TypeInferenceError) -> Self {
|
|
||||||
match value {
|
|
||||||
TypeInferenceError::ConstantTooLarge(loc, primty, value) => loc
|
|
||||||
.labelled_error("constant too large for type")
|
|
||||||
.with_message(format!(
|
|
||||||
"Type {} has a max value of {}, which is smaller than {}",
|
|
||||||
primty,
|
|
||||||
primty.max_value().expect("constant type has max value"),
|
|
||||||
value
|
|
||||||
)),
|
|
||||||
TypeInferenceError::NotANumber(loc, primty) => loc
|
|
||||||
.labelled_error("not a numeric type")
|
|
||||||
.with_message(format!(
|
|
||||||
"For some reason, we're trying to use {} as a numeric type",
|
|
||||||
primty,
|
|
||||||
)),
|
|
||||||
TypeInferenceError::NotEquivalent(loc, ty1, ty2) => loc
|
|
||||||
.labelled_error("type inference error")
|
|
||||||
.with_message(format!("Expected type {}, received type {}", ty1, ty2)),
|
|
||||||
TypeInferenceError::CannotSafelyCast(loc, ty1, ty2) => loc
|
|
||||||
.labelled_error("unsafe type cast")
|
|
||||||
.with_message(format!("Cannot safely cast {} to {}", ty1, ty2)),
|
|
||||||
TypeInferenceError::WrongPrimitiveArity(loc, prim, lower, upper, observed) => loc
|
|
||||||
.labelled_error("wrong number of arguments")
|
|
||||||
.with_message(format!(
|
|
||||||
"expected {} for {}, received {}",
|
|
||||||
if lower == upper && lower > 1 {
|
|
||||||
format!("{} arguments", lower)
|
|
||||||
} else if lower == upper {
|
|
||||||
format!("{} argument", lower)
|
|
||||||
} else {
|
|
||||||
format!("{}-{} arguments", lower, upper)
|
|
||||||
},
|
|
||||||
prim,
|
|
||||||
observed
|
|
||||||
)),
|
|
||||||
TypeInferenceError::CannotCast(loc, t1, t2) => loc
|
|
||||||
.labelled_error("cannot cast between types")
|
|
||||||
.with_message(format!(
|
|
||||||
"tried to cast from {} to {}",
|
|
||||||
t1, t2,
|
|
||||||
)),
|
|
||||||
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::CannotMakeNumberAStructure(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::FunctionsAreNotPrintable(loc) => loc
|
|
||||||
.labelled_error("cannot print function values"),
|
|
||||||
TypeInferenceError::IsNotSigned(loc, pt) => loc
|
|
||||||
.labelled_error(format!("type {} is not signed", pt))
|
|
||||||
.with_message("and so it cannot be negated"),
|
|
||||||
TypeInferenceError::NoFieldForType(loc, field, t) => loc
|
|
||||||
.labelled_error(format!("no field {} available for type {}", field, t)),
|
|
||||||
TypeInferenceError::UnknownTypeName(loc , name) => loc
|
|
||||||
.labelled_error(format!("unknown type named {}", name)),
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::CanCastTo(loc, a, b)) => {
|
|
||||||
loc.labelled_error("internal error").with_message(format!(
|
|
||||||
"could not determine if it was safe to cast from {} to {}",
|
|
||||||
a, b
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::TypeHasField(loc, a, field, _)) => {
|
|
||||||
loc.labelled_error("internal error")
|
|
||||||
.with_message(format!("fould not determine if type {} has field {}", a, field))
|
|
||||||
}
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::Equivalent(loc, a, b)) => {
|
|
||||||
loc.labelled_error("internal error").with_message(format!(
|
|
||||||
"could not determine if {} and {} were equivalent",
|
|
||||||
a, b
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::FitsInNumType(loc, ty, val)) => {
|
|
||||||
loc.labelled_error("internal error").with_message(format!(
|
|
||||||
"Could not determine if {} could fit in {}",
|
|
||||||
val, ty
|
|
||||||
))
|
|
||||||
}
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::NumericType(loc, ty)) => loc
|
|
||||||
.labelled_error("internal error")
|
|
||||||
.with_message(format!("Could not determine if {} was a numeric type", ty)),
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::ConstantNumericType(loc, ty)) =>
|
|
||||||
panic!("What? Constants should always eventually be solved, even by default; {:?} and type {:?}", loc, ty),
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::Printable(loc, ty)) => loc
|
|
||||||
.labelled_error("internal error")
|
|
||||||
.with_message(format!("Could not determine if type {} was printable", ty)),
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::IsSomething(loc, _)) => {
|
|
||||||
loc.labelled_error("could not infer type")
|
|
||||||
.with_message("Could not find *any* type information; is this an unused function argument?")
|
|
||||||
}
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::IsSigned(loc, t)) => loc
|
|
||||||
.labelled_error("internal error")
|
|
||||||
.with_message(format!("could not infer that type {} was signed", t)),
|
|
||||||
TypeInferenceError::CouldNotSolve(Constraint::NamedTypeIs(loc, name, ty)) => loc
|
|
||||||
.labelled_error("internal error")
|
|
||||||
.with_message(format!("could not infer that the name {} refers to {}", name, ty)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
DefaultedTo(Location, TypeOrVar),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TypeInferenceWarning> for Diagnostic<usize> {
|
|
||||||
fn from(value: TypeInferenceWarning) -> Self {
|
|
||||||
match value {
|
|
||||||
TypeInferenceWarning::DefaultedTo(loc, ty) => Diagnostic::warning()
|
|
||||||
.with_labels(vec![loc.primary_label().with_message("unknown type")])
|
|
||||||
.with_message(format!("Defaulted unknown type to {}", ty)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl super::InferenceEngine {
|
||||||
/// Solve all the constraints in the provided database.
|
/// 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,
|
/// This process can take a bit, so you might not want to do it multiple times. Basically,
|
||||||
@@ -298,12 +16,7 @@ impl From<TypeInferenceWarning> for Diagnostic<usize> {
|
|||||||
/// The return value is a type inference result, which pairs some warnings with either a
|
/// 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
|
/// successful set of type resolutions (mappings from type variables to their values), or
|
||||||
/// a series of inference errors.
|
/// a series of inference errors.
|
||||||
pub fn solve_constraints(
|
pub fn solve_constraints(&mut self) {
|
||||||
known_types: &HashMap<ArcIntern<String>, TypeOrVar>,
|
|
||||||
mut constraint_db: Vec<Constraint>,
|
|
||||||
) -> TypeInferenceResult<TypeResolutions> {
|
|
||||||
let mut errors = vec![];
|
|
||||||
let mut warnings = vec![];
|
|
||||||
let mut iteration = 0u64;
|
let mut iteration = 0u64;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -312,12 +25,12 @@ pub fn solve_constraints(
|
|||||||
let mut new_constraints = vec![];
|
let mut new_constraints = vec![];
|
||||||
|
|
||||||
tracing::debug!(iteration, "Restarting constraint solving loop");
|
tracing::debug!(iteration, "Restarting constraint solving loop");
|
||||||
for constraint in constraint_db.iter() {
|
for constraint in self.constraints.iter() {
|
||||||
tracing::debug!(%constraint, "remaining constraint");
|
tracing::debug!(%constraint, "remaining constraint");
|
||||||
}
|
}
|
||||||
iteration += 1;
|
iteration += 1;
|
||||||
|
|
||||||
while let Some(constraint) = constraint_db.pop() {
|
while let Some(constraint) = self.constraints.pop() {
|
||||||
match constraint {
|
match constraint {
|
||||||
// The basic philosophy of this match block is that, for each constraint, we're
|
// The basic philosophy of this match block is that, for each constraint, we're
|
||||||
// going to start seeing if we can just solve (or abandon) the constraint. Then,
|
// going to start seeing if we can just solve (or abandon) the constraint. Then,
|
||||||
@@ -330,7 +43,7 @@ pub fn solve_constraints(
|
|||||||
TypeOrVar::Primitive(to_type),
|
TypeOrVar::Primitive(to_type),
|
||||||
) => {
|
) => {
|
||||||
if !from_type.can_cast_to(&to_type) {
|
if !from_type.can_cast_to(&to_type) {
|
||||||
errors.push(TypeInferenceError::CannotSafelyCast(
|
self.errors.push(TypeInferenceError::CannotSafelyCast(
|
||||||
loc, from_type, to_type,
|
loc, from_type, to_type,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -346,11 +59,15 @@ pub fn solve_constraints(
|
|||||||
if args1.len() == args2.len() {
|
if args1.len() == args2.len() {
|
||||||
new_constraints.push(Constraint::Equivalent(loc.clone(), *ret1, *ret2));
|
new_constraints.push(Constraint::Equivalent(loc.clone(), *ret1, *ret2));
|
||||||
for (arg1, arg2) in args1.into_iter().zip(args2) {
|
for (arg1, arg2) in args1.into_iter().zip(args2) {
|
||||||
new_constraints.push(Constraint::Equivalent(loc.clone(), arg1, arg2))
|
new_constraints.push(Constraint::Equivalent(
|
||||||
|
loc.clone(),
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
all_constraints_solved = false;
|
all_constraints_solved = false;
|
||||||
} else {
|
} else {
|
||||||
errors.push(TypeInferenceError::CannotCast(
|
self.errors.push(TypeInferenceError::CannotCast(
|
||||||
loc,
|
loc,
|
||||||
TypeOrVar::Function(args1, ret1),
|
TypeOrVar::Function(args1, ret1),
|
||||||
TypeOrVar::Function(args2, ret2),
|
TypeOrVar::Function(args2, ret2),
|
||||||
@@ -380,7 +97,8 @@ pub fn solve_constraints(
|
|||||||
ot @ TypeOrVar::Primitive(_) | ot @ TypeOrVar::Structure(_),
|
ot @ TypeOrVar::Primitive(_) | ot @ TypeOrVar::Structure(_),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(function_type = %ft, other_type = %ot, "we can't cast a function type to a primitive or structure type");
|
tracing::trace!(function_type = %ft, other_type = %ot, "we can't cast a function type to a primitive or structure type");
|
||||||
errors.push(TypeInferenceError::CannotCast(loc, ft, ot));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotCast(loc, ft, ot));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +108,8 @@ pub fn solve_constraints(
|
|||||||
ot @ TypeOrVar::Function(_, _) | ot @ TypeOrVar::Structure(_),
|
ot @ TypeOrVar::Function(_, _) | ot @ TypeOrVar::Structure(_),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(other_type = %ot, primitive_type = %pt, "we can't cast a primitive type to a function or structure type");
|
tracing::trace!(other_type = %ot, primitive_type = %pt, "we can't cast a primitive type to a function or structure type");
|
||||||
errors.push(TypeInferenceError::CannotCast(loc, pt, ot));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotCast(loc, pt, ot));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,14 +119,17 @@ pub fn solve_constraints(
|
|||||||
ot @ TypeOrVar::Primitive(_) | ot @ TypeOrVar::Function(_, _),
|
ot @ TypeOrVar::Primitive(_) | ot @ TypeOrVar::Function(_, _),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(structure_type = %st, other_type = %ot, "we can't cast a structure type to a function or primitive type");
|
tracing::trace!(structure_type = %st, other_type = %ot, "we can't cast a structure type to a function or primitive type");
|
||||||
errors.push(TypeInferenceError::CannotCast(loc, st, ot));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotCast(loc, st, ot));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::NamedTypeIs(loc, name, ty) => match known_types.get(&name) {
|
Constraint::NamedTypeIs(loc, name, ty) => {
|
||||||
|
match self.type_definitions.get(&name) {
|
||||||
None => {
|
None => {
|
||||||
tracing::trace!(type_name = %name, "we don't know a type named name");
|
tracing::trace!(type_name = %name, "we don't know a type named name");
|
||||||
errors.push(TypeInferenceError::UnknownTypeName(loc, name));
|
self.errors
|
||||||
|
.push(TypeInferenceError::UnknownTypeName(loc, name));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +142,8 @@ pub fn solve_constraints(
|
|||||||
));
|
));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Constraint::TypeHasField(
|
Constraint::TypeHasField(
|
||||||
loc,
|
loc,
|
||||||
@@ -431,7 +154,7 @@ pub fn solve_constraints(
|
|||||||
None => {
|
None => {
|
||||||
let reconstituted = TypeOrVar::Structure(fields);
|
let reconstituted = TypeOrVar::Structure(fields);
|
||||||
tracing::trace!(structure_type = %reconstituted, %field, "no field found in type");
|
tracing::trace!(structure_type = %reconstituted, %field, "no field found in type");
|
||||||
errors.push(TypeInferenceError::NoFieldForType(
|
self.errors.push(TypeInferenceError::NoFieldForType(
|
||||||
loc,
|
loc,
|
||||||
field,
|
field,
|
||||||
reconstituted,
|
reconstituted,
|
||||||
@@ -457,7 +180,8 @@ pub fn solve_constraints(
|
|||||||
_,
|
_,
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(other_type = %ot, %field, "can't get field from primitive or function type");
|
tracing::trace!(other_type = %ot, %field, "can't get field from primitive or function type");
|
||||||
errors.push(TypeInferenceError::NoFieldForType(loc, field, ot));
|
self.errors
|
||||||
|
.push(TypeInferenceError::NoFieldForType(loc, field, ot));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +189,7 @@ pub fn solve_constraints(
|
|||||||
Constraint::ConstantNumericType(loc, TypeOrVar::Primitive(pt)) => {
|
Constraint::ConstantNumericType(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
tracing::trace!(primitive_type = %pt, "its easy to tell if a constant number can be a primitive type");
|
tracing::trace!(primitive_type = %pt, "its easy to tell if a constant number can be a primitive type");
|
||||||
if pt.max_value().is_none() {
|
if pt.max_value().is_none() {
|
||||||
errors.push(TypeInferenceError::NotANumber(loc, pt))
|
self.errors.push(TypeInferenceError::NotANumber(loc, pt))
|
||||||
}
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
@@ -473,24 +197,27 @@ pub fn solve_constraints(
|
|||||||
// if we're testing if a function type is numeric, then throw a useful warning
|
// if we're testing if a function type is numeric, then throw a useful warning
|
||||||
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
tracing::trace!(function_type = %t, "functions can't be constant numbers");
|
tracing::trace!(function_type = %t, "functions can't be constant numbers");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a function type is numeric, then throw a useful warning
|
// if we're testing if a function type is numeric, then throw a useful warning
|
||||||
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Structure(_)) => {
|
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Structure(_)) => {
|
||||||
tracing::trace!(structure_type = %t, "structures can't be constant numbers");
|
tracing::trace!(structure_type = %t, "structures can't be constant numbers");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAStructure(loc, t, None));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAStructure(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a number can fit into a numeric type, we can just do that!
|
// if we're testing if a number can fit into a numeric type, we can just do that!
|
||||||
Constraint::FitsInNumType(loc, TypeOrVar::Primitive(ctype), val) => {
|
Constraint::FitsInNumType(loc, TypeOrVar::Primitive(ctype), val) => {
|
||||||
match ctype.max_value() {
|
match ctype.max_value() {
|
||||||
None => errors.push(TypeInferenceError::NotANumber(loc, ctype)),
|
None => self.errors.push(TypeInferenceError::NotANumber(loc, ctype)),
|
||||||
|
|
||||||
Some(max_value) if max_value < val => {
|
Some(max_value) if max_value < val => {
|
||||||
errors.push(TypeInferenceError::ConstantTooLarge(loc, ctype, val));
|
self.errors
|
||||||
|
.push(TypeInferenceError::ConstantTooLarge(loc, ctype, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
@@ -502,7 +229,8 @@ pub fn solve_constraints(
|
|||||||
// if we're testing if a function type can fit into a numeric type, that's a problem
|
// if we're testing if a function type can fit into a numeric type, that's a problem
|
||||||
Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => {
|
Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => {
|
||||||
tracing::trace!(function_type = %t, "values don't fit in function types");
|
tracing::trace!(function_type = %t, "values don't fit in function types");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAFunction(
|
||||||
loc,
|
loc,
|
||||||
t,
|
t,
|
||||||
Some(val),
|
Some(val),
|
||||||
@@ -513,7 +241,8 @@ pub fn solve_constraints(
|
|||||||
// if we're testing if a function type can fit into a numeric type, that's a problem
|
// if we're testing if a function type can fit into a numeric type, that's a problem
|
||||||
Constraint::FitsInNumType(loc, t @ TypeOrVar::Structure(_), val) => {
|
Constraint::FitsInNumType(loc, t @ TypeOrVar::Structure(_), val) => {
|
||||||
tracing::trace!(function_type = %t, "values don't fit in structure types");
|
tracing::trace!(function_type = %t, "values don't fit in structure types");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAStructure(
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAStructure(
|
||||||
loc,
|
loc,
|
||||||
t,
|
t,
|
||||||
Some(val),
|
Some(val),
|
||||||
@@ -533,7 +262,7 @@ pub fn solve_constraints(
|
|||||||
Constraint::IsSigned(loc, TypeOrVar::Primitive(pt)) => {
|
Constraint::IsSigned(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
tracing::trace!(primitive_type = %pt, "we can check if a primitive is signed");
|
tracing::trace!(primitive_type = %pt, "we can check if a primitive is signed");
|
||||||
if !pt.valid_operators().contains(&("-", 1)) {
|
if !pt.valid_operators().contains(&("-", 1)) {
|
||||||
errors.push(TypeInferenceError::IsNotSigned(
|
self.errors.push(TypeInferenceError::IsNotSigned(
|
||||||
loc,
|
loc,
|
||||||
TypeOrVar::Primitive(pt),
|
TypeOrVar::Primitive(pt),
|
||||||
));
|
));
|
||||||
@@ -544,14 +273,14 @@ pub fn solve_constraints(
|
|||||||
// again with the functions and the numbers
|
// again with the functions and the numbers
|
||||||
Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
tracing::trace!(function_type = %t, "functions are not signed");
|
tracing::trace!(function_type = %t, "functions are not signed");
|
||||||
errors.push(TypeInferenceError::IsNotSigned(loc, t));
|
self.errors.push(TypeInferenceError::IsNotSigned(loc, t));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// again with the functions and the numbers
|
// again with the functions and the numbers
|
||||||
Constraint::IsSigned(loc, t @ TypeOrVar::Structure(_)) => {
|
Constraint::IsSigned(loc, t @ TypeOrVar::Structure(_)) => {
|
||||||
tracing::trace!(structure_type = %t, "structures are not signed");
|
tracing::trace!(structure_type = %t, "structures are not signed");
|
||||||
errors.push(TypeInferenceError::IsNotSigned(loc, t));
|
self.errors.push(TypeInferenceError::IsNotSigned(loc, t));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +288,7 @@ pub fn solve_constraints(
|
|||||||
Constraint::NumericType(loc, TypeOrVar::Primitive(pt)) => {
|
Constraint::NumericType(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
tracing::trace!(primitive_type = %pt, "its easy to tell if a primitive type is numeric");
|
tracing::trace!(primitive_type = %pt, "its easy to tell if a primitive type is numeric");
|
||||||
if pt.max_value().is_none() {
|
if pt.max_value().is_none() {
|
||||||
errors.push(TypeInferenceError::NotANumber(loc, pt))
|
self.errors.push(TypeInferenceError::NotANumber(loc, pt))
|
||||||
}
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
@@ -567,14 +296,16 @@ pub fn solve_constraints(
|
|||||||
// if we're testing if a function type is numeric, then throw a useful warning
|
// if we're testing if a function type is numeric, then throw a useful warning
|
||||||
Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
tracing::trace!(function_type = %t, "function types aren't numeric");
|
tracing::trace!(function_type = %t, "function types aren't numeric");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a structure type is numeric, then throw a useful warning
|
// if we're testing if a structure type is numeric, then throw a useful warning
|
||||||
Constraint::NumericType(loc, t @ TypeOrVar::Structure(_)) => {
|
Constraint::NumericType(loc, t @ TypeOrVar::Structure(_)) => {
|
||||||
tracing::trace!(structure_type = %t, "structure types aren't numeric");
|
tracing::trace!(structure_type = %t, "structure types aren't numeric");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAStructure(loc, t, None));
|
self.errors
|
||||||
|
.push(TypeInferenceError::CannotMakeNumberAStructure(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +318,8 @@ pub fn solve_constraints(
|
|||||||
// function types are definitely not printable
|
// function types are definitely not printable
|
||||||
Constraint::Printable(loc, ft @ TypeOrVar::Function(_, _)) => {
|
Constraint::Printable(loc, ft @ TypeOrVar::Function(_, _)) => {
|
||||||
tracing::trace!(function_type = %ft, "function types are not printable");
|
tracing::trace!(function_type = %ft, "function types are not printable");
|
||||||
errors.push(TypeInferenceError::FunctionsAreNotPrintable(loc));
|
self.errors
|
||||||
|
.push(TypeInferenceError::FunctionsAreNotPrintable(loc));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +341,7 @@ pub fn solve_constraints(
|
|||||||
TypeOrVar::Primitive(pt2),
|
TypeOrVar::Primitive(pt2),
|
||||||
) => {
|
) => {
|
||||||
if pt1 != pt2 {
|
if pt1 != pt2 {
|
||||||
errors.push(TypeInferenceError::NotEquivalent(
|
self.errors.push(TypeInferenceError::NotEquivalent(
|
||||||
loc,
|
loc,
|
||||||
TypeOrVar::Primitive(pt1),
|
TypeOrVar::Primitive(pt1),
|
||||||
TypeOrVar::Primitive(pt2),
|
TypeOrVar::Primitive(pt2),
|
||||||
@@ -630,7 +362,8 @@ pub fn solve_constraints(
|
|||||||
pt @ TypeOrVar::Primitive(_),
|
pt @ TypeOrVar::Primitive(_),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(primitive_type = %pt, function_type = %ft, "function and primitive types cannot be equivalent");
|
tracing::trace!(primitive_type = %pt, function_type = %ft, "function and primitive types cannot be equivalent");
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, pt, ft));
|
self.errors
|
||||||
|
.push(TypeInferenceError::NotEquivalent(loc, pt, ft));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +378,8 @@ pub fn solve_constraints(
|
|||||||
pt @ TypeOrVar::Primitive(_),
|
pt @ TypeOrVar::Primitive(_),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(primitive_type = %pt, structure_type = %st, "structure and primitive types cannot be equivalent");
|
tracing::trace!(primitive_type = %pt, structure_type = %st, "structure and primitive types cannot be equivalent");
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, pt, st));
|
self.errors
|
||||||
|
.push(TypeInferenceError::NotEquivalent(loc, pt, st));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +394,8 @@ pub fn solve_constraints(
|
|||||||
st @ TypeOrVar::Structure(_),
|
st @ TypeOrVar::Structure(_),
|
||||||
) => {
|
) => {
|
||||||
tracing::trace!(structure_type = %st, function_type = %ft, "structure and primitive types cannot be equivalent");
|
tracing::trace!(structure_type = %st, function_type = %ft, "structure and primitive types cannot be equivalent");
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, st, ft));
|
self.errors
|
||||||
|
.push(TypeInferenceError::NotEquivalent(loc, st, ft));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,10 +416,15 @@ pub fn solve_constraints(
|
|||||||
if args1.len() != args2.len() {
|
if args1.len() != args2.len() {
|
||||||
let t1 = TypeOrVar::Function(args1, ret1);
|
let t1 = TypeOrVar::Function(args1, ret1);
|
||||||
let t2 = TypeOrVar::Function(args2, ret2);
|
let t2 = TypeOrVar::Function(args2, ret2);
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, t1, t2));
|
self.errors
|
||||||
|
.push(TypeInferenceError::NotEquivalent(loc, t1, t2));
|
||||||
} else {
|
} else {
|
||||||
for (left, right) in args1.into_iter().zip(args2) {
|
for (left, right) in args1.into_iter().zip(args2) {
|
||||||
new_constraints.push(Constraint::Equivalent(loc.clone(), left, right));
|
new_constraints.push(Constraint::Equivalent(
|
||||||
|
loc.clone(),
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
new_constraints.push(Constraint::Equivalent(loc, *ret1, *ret2));
|
new_constraints.push(Constraint::Equivalent(loc, *ret1, *ret2));
|
||||||
all_constraints_solved = false;
|
all_constraints_solved = false;
|
||||||
@@ -713,7 +453,7 @@ pub fn solve_constraints(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.push(TypeInferenceError::NotEquivalent(
|
self.errors.push(TypeInferenceError::NotEquivalent(
|
||||||
loc,
|
loc,
|
||||||
TypeOrVar::Structure(fields1),
|
TypeOrVar::Structure(fields1),
|
||||||
TypeOrVar::Structure(fields2),
|
TypeOrVar::Structure(fields2),
|
||||||
@@ -724,7 +464,7 @@ pub fn solve_constraints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Constraint::Equivalent(_, TypeOrVar::Variable(_, ref name), ref rhs) => {
|
Constraint::Equivalent(_, TypeOrVar::Variable(_, ref name), ref rhs) => {
|
||||||
changed_something |= replace_variable(&mut constraint_db, name, rhs);
|
changed_something |= replace_variable(&mut self.constraints, name, rhs);
|
||||||
changed_something |= replace_variable(&mut new_constraints, name, rhs);
|
changed_something |= replace_variable(&mut new_constraints, name, rhs);
|
||||||
all_constraints_solved &= rhs.is_resolved();
|
all_constraints_solved &= rhs.is_resolved();
|
||||||
if changed_something {
|
if changed_something {
|
||||||
@@ -756,19 +496,7 @@ pub fn solve_constraints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if all_constraints_solved {
|
if all_constraints_solved {
|
||||||
let result = new_constraints
|
return;
|
||||||
.into_iter()
|
|
||||||
.map(|constraint| match constraint {
|
|
||||||
Constraint::Equivalent(_, TypeOrVar::Variable(_, name), result) => {
|
|
||||||
match result.try_into() {
|
|
||||||
Err(e) => panic!("Ended up with complex type {}", e),
|
|
||||||
Ok(v) => (name, v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("Had something that wasn't an equivalence left at the end!"),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
return TypeInferenceResult::Success { result, warnings };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changed_something {
|
if !changed_something {
|
||||||
@@ -782,7 +510,8 @@ pub fn solve_constraints(
|
|||||||
$t.clone(),
|
$t.clone(),
|
||||||
resty.clone(),
|
resty.clone(),
|
||||||
));
|
));
|
||||||
warnings.push(TypeInferenceWarning::DefaultedTo($loc.clone(), resty));
|
self.warnings
|
||||||
|
.push(TypeInferenceWarning::DefaultedTo($loc.clone(), resty));
|
||||||
tracing::trace!("Adding number equivalence");
|
tracing::trace!("Adding number equivalence");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -808,19 +537,20 @@ pub fn solve_constraints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if addendums.is_empty() {
|
if addendums.is_empty() {
|
||||||
if errors.is_empty() {
|
if self.errors.is_empty() {
|
||||||
errors = new_constraints
|
self.errors = new_constraints
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(TypeInferenceError::CouldNotSolve)
|
.map(TypeInferenceError::CouldNotSolve)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
return TypeInferenceResult::Failure { errors, warnings };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_constraints.append(&mut addendums);
|
new_constraints.append(&mut addendums);
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint_db = new_constraints;
|
self.constraints = new_constraints;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/type_infer/warning.rs
Normal file
21
src/type_infer/warning.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use crate::ir::TypeOrVar;
|
||||||
|
use crate::syntax::Location;
|
||||||
|
use codespan_reporting::diagnostic::Diagnostic;
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
DefaultedTo(Location, TypeOrVar),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypeInferenceWarning> for Diagnostic<usize> {
|
||||||
|
fn from(value: TypeInferenceWarning) -> Self {
|
||||||
|
match value {
|
||||||
|
TypeInferenceWarning::DefaultedTo(loc, ty) => Diagnostic::warning()
|
||||||
|
.with_labels(vec![loc.primary_label().with_message("unknown type")])
|
||||||
|
.with_message(format!("Defaulted unknown type to {}", ty)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user