feat: start expr inference

This commit is contained in:
rvcas 2022-10-19 23:23:23 -04:00 committed by Lucas
parent 81c87ab4da
commit cabc653167
12 changed files with 2373 additions and 472 deletions

16
Cargo.lock generated
View File

@ -77,6 +77,7 @@ version = "0.0.20"
dependencies = [
"chumsky",
"internment",
"itertools",
"miette",
"pretty_assertions",
"thiserror",
@ -294,6 +295,12 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "fastrand"
version = "1.8.0"
@ -443,6 +450,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.3"

View File

@ -4,7 +4,12 @@ use std::{
path::{Path, PathBuf},
};
use aiken_lang::{ast::ModuleKind, builtins, tipo, IdGenerator};
use aiken_lang::{
ast::ModuleKind,
builtins,
tipo::{self, TypeInfo},
IdGenerator,
};
use crate::{
config::Config,
@ -39,7 +44,7 @@ pub struct Project {
config: Config,
defined_modules: HashMap<String, PathBuf>,
id_gen: IdGenerator,
module_types: HashMap<String, tipo::Module>,
module_types: HashMap<String, TypeInfo>,
root: PathBuf,
sources: Vec<Source>,
pub warnings: Vec<Warning>,
@ -164,19 +169,19 @@ impl Project {
{
let mut type_warnings = Vec::new();
let ast = tipo::infer::module(
&self.id_gen,
ast,
kind,
&self.config.name,
&self.module_types,
&mut type_warnings,
)
.map_err(|error| Error::Type {
path: path.clone(),
src: code.clone(),
error,
})?;
let ast = ast
.infer(
&self.id_gen,
kind,
&self.config.name,
&self.module_types,
&mut type_warnings,
)
.map_err(|error| Error::Type {
path: path.clone(),
src: code.clone(),
error,
})?;
// Register any warnings emitted as type warnings
let type_warnings = type_warnings

View File

@ -13,6 +13,7 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
[dependencies]
chumsky = "0.8.0"
internment = "0.7.0"
itertools = "0.10.5"
miette = "5.2.0"
thiserror = "1.0.37"
vec1 = "1.8.0"

View File

@ -1,15 +1,19 @@
use std::{collections::HashMap, fmt, ops::Range, sync::Arc};
use std::{fmt, ops::Range, sync::Arc};
use internment::Intern;
use crate::{
builtins,
expr::{TypedExpr, UntypedExpr},
tipo::{self, PatternConstructor, Type, ValueConstructor},
tipo::{fields::FieldMap, PatternConstructor, Type, TypeInfo, ValueConstructor},
};
pub const ASSERT_VARIABLE: &str = "_try";
pub const CAPTURE_VARIABLE: &str = "_capture";
pub const PIPE_VARIABLE: &str = "_pipe";
pub const TRY_VARIABLE: &str = "_try";
pub type TypedModule = Module<tipo::Module, TypedDefinition>;
pub type TypedModule = Module<TypeInfo, TypedDefinition>;
pub type UntypedModule = Module<(), UntypedDefinition>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -133,11 +137,6 @@ pub enum Constant<T, RecordTag> {
value: String,
},
Pair {
location: Span,
elements: Vec<Self>,
},
List {
location: Span,
elements: Vec<Self>,
@ -154,9 +153,9 @@ pub enum Constant<T, RecordTag> {
field_map: Option<FieldMap>,
},
ByteString {
ByteArray {
location: Span,
// segments: Vec<BitStringSegment<Self, T>>,
bytes: Vec<u8>,
},
Var {
@ -168,6 +167,39 @@ pub enum Constant<T, RecordTag> {
},
}
impl TypedConstant {
pub fn tipo(&self) -> Arc<Type> {
match self {
Constant::Int { .. } => builtins::int(),
Constant::String { .. } => builtins::string(),
Constant::ByteArray { .. } => builtins::byte_array(),
Constant::List { tipo, .. }
| Constant::Record { tipo, .. }
| Constant::Var { tipo, .. } => tipo.clone(),
}
}
}
impl<A, B> Constant<A, B> {
pub fn location(&self) -> Span {
match self {
Constant::Int { location, .. }
| Constant::List { location, .. }
| Constant::String { location, .. }
| Constant::Record { location, .. }
| Constant::ByteArray { location, .. }
| Constant::Var { location, .. } => *location,
}
}
pub fn is_simple(&self) -> bool {
matches!(
self,
Self::Int { .. } | Self::ByteArray { .. } | Self::String { .. }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallArg<A> {
pub label: Option<String>,
@ -175,44 +207,6 @@ pub struct CallArg<A> {
pub value: A,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldMap {
pub arity: usize,
pub fields: HashMap<String, usize>,
}
impl FieldMap {
pub fn new(arity: usize) -> Self {
Self {
arity,
fields: HashMap::new(),
}
}
pub fn insert(
&mut self,
label: String,
index: usize,
location: &Span,
) -> Result<(), tipo::error::Error> {
match self.fields.insert(label.clone(), index) {
Some(_) => Err(tipo::error::Error::DuplicateField {
label,
location: *location,
}),
None => Ok(()),
}
}
pub fn into_option(self) -> Option<Self> {
if self.fields.is_empty() {
None
} else {
Some(self)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordConstructor<T> {
pub location: Span,
@ -232,6 +226,7 @@ pub struct RecordConstructorArg<T> {
pub doc: Option<String>,
}
pub type TypedArg = Arg<Arc<Type>>;
pub type UntypedArg = Arg<()>;
#[derive(Debug, Clone, PartialEq)]
@ -242,6 +237,21 @@ pub struct Arg<T> {
pub tipo: T,
}
impl<A> Arg<A> {
pub fn set_type<B>(self, tipo: B) -> Arg<B> {
Arg {
tipo,
arg_name: self.arg_name,
location: self.location,
annotation: self.annotation,
}
}
pub fn get_variable_name(&self) -> Option<&str> {
self.arg_name.get_variable_name()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgName {
Discard {
@ -264,6 +274,15 @@ pub enum ArgName {
},
}
impl ArgName {
pub fn get_variable_name(&self) -> Option<&str> {
match self {
ArgName::Discard { .. } | ArgName::LabeledDiscard { .. } => None,
ArgName::NamedLabeled { name, .. } | ArgName::Named { name, .. } => Some(name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnqualifiedImport {
pub location: Span,
@ -272,6 +291,16 @@ pub struct UnqualifiedImport {
pub layer: Layer,
}
impl UnqualifiedImport {
pub fn variable_name(&self) -> &str {
self.as_name.as_deref().unwrap_or(&self.name)
}
pub fn is_value(&self) -> bool {
self.layer.is_value()
}
}
// TypeAst
#[derive(Debug, Clone, PartialEq)]
pub enum Annotation {
@ -383,6 +412,13 @@ impl Default for Layer {
}
}
impl Layer {
/// Returns `true` if the layer is [`Value`].
pub fn is_value(&self) -> bool {
matches!(self, Self::Value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
// Boolean logic

View File

@ -2,7 +2,10 @@ use std::{cell::RefCell, collections::HashMap, sync::Arc};
use crate::{
ast::{ModuleKind, Span},
tipo::{self, Type, TypeConstructor, TypeVar, ValueConstructor, ValueConstructorVariant},
tipo::{
self, fields::FieldMap, Type, TypeConstructor, TypeVar, ValueConstructor,
ValueConstructorVariant,
},
IdGenerator,
};
@ -16,9 +19,9 @@ const STRING: &str = "String";
/// Build a prelude that can be injected
/// into a compiler pipeline
pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
let mut prelude = tipo::Module {
name: vec!["aiken".to_string()],
pub fn prelude(id_gen: &IdGenerator) -> tipo::TypeInfo {
let mut prelude = tipo::TypeInfo {
name: "aiken".to_string(),
package: "".to_string(),
kind: ModuleKind::Lib,
types: HashMap::new(),
@ -64,7 +67,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
ValueConstructorVariant::Record {
module: "".into(),
name: "True".to_string(),
field_map: None,
field_map: None::<FieldMap>,
arity: 0,
location: Span::empty(),
constructors_count: 2,
@ -79,7 +82,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
ValueConstructorVariant::Record {
module: "".into(),
name: "False".to_string(),
field_map: None,
field_map: None::<FieldMap>,
arity: 0,
location: Span::empty(),
constructors_count: 2,
@ -132,7 +135,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
module: "".into(),
name: NIL.to_string(),
arity: 0,
field_map: None,
field_map: None::<FieldMap>,
location: Span::empty(),
constructors_count: 1,
},
@ -179,7 +182,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
ValueConstructorVariant::Record {
module: "".into(),
name: "Ok".to_string(),
field_map: None,
field_map: None::<FieldMap>,
arity: 1,
location: Span::empty(),
constructors_count: 2,
@ -196,7 +199,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
ValueConstructorVariant::Record {
module: "".into(),
name: "Error".to_string(),
field_map: None,
field_map: None::<FieldMap>,
arity: 1,
location: Span::empty(),
constructors_count: 2,

View File

@ -7,6 +7,7 @@ use crate::{
Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, IfBranch, Pattern,
RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UntypedRecordUpdateArg,
},
builtins::{bool, nil},
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
};
@ -18,16 +19,16 @@ pub enum TypedExpr {
value: String,
},
Float {
String {
location: Span,
tipo: Arc<Type>,
value: String,
},
String {
ByteArray {
location: Span,
tipo: Arc<Type>,
value: String,
bytes: Vec<u8>,
},
Sequence {
@ -161,6 +162,101 @@ pub enum TypedExpr {
},
}
impl TypedExpr {
pub fn tipo(&self) -> Arc<Type> {
match self {
Self::Negate { .. } => bool(),
Self::Var { constructor, .. } => constructor.tipo.clone(),
Self::Try { then, .. } => then.tipo(),
Self::Fn { tipo, .. }
| Self::Int { tipo, .. }
| Self::Todo { tipo, .. }
| Self::When { tipo, .. }
| Self::List { tipo, .. }
| Self::Call { tipo, .. }
| Self::If { tipo, .. }
| Self::BinOp { tipo, .. }
| Self::Tuple { tipo, .. }
| Self::String { tipo, .. }
| Self::ByteArray { tipo, .. }
| Self::TupleIndex { tipo, .. }
| Self::Assignment { tipo, .. }
| Self::ModuleSelect { tipo, .. }
| Self::RecordAccess { tipo, .. }
| Self::RecordUpdate { tipo, .. } => tipo.clone(),
Self::Pipeline { expressions, .. } | Self::Sequence { expressions, .. } => {
expressions.last().map(TypedExpr::tipo).unwrap_or_else(nil)
}
}
}
pub fn type_defining_location(&self) -> Span {
match self {
Self::Fn { location, .. }
| Self::Int { location, .. }
| Self::Try { location, .. }
| Self::Var { location, .. }
| Self::Todo { location, .. }
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::Negate { location, .. }
| Self::Pipeline { location, .. }
| Self::ByteArray { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location,
Self::If {
location,
branches,
final_else,
tipo,
} => branches.first().body.type_defining_location(),
Self::Sequence {
expressions,
location,
..
} => expressions
.last()
.map(TypedExpr::location)
.unwrap_or(*location),
}
}
pub fn location(&self) -> Span {
match self {
Self::Fn { location, .. }
| Self::Try { location, .. }
| Self::Int { location, .. }
| Self::Var { location, .. }
| Self::Todo { location, .. }
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::If { location, .. }
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::Negate { location, .. }
| Self::Sequence { location, .. }
| Self::Pipeline { location, .. }
| Self::ByteArray { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UntypedExpr {
Int {
@ -168,11 +264,6 @@ pub enum UntypedExpr {
value: String,
},
Float {
location: Span,
value: String,
},
String {
location: Span,
value: String,
@ -203,9 +294,9 @@ pub enum UntypedExpr {
},
Call {
location: Span,
fun: Box<Self>,
arguments: Vec<CallArg<Self>>,
fun: Box<Self>,
location: Span,
},
BinOp {
@ -215,6 +306,11 @@ pub enum UntypedExpr {
right: Box<Self>,
},
ByteArray {
location: Span,
bytes: Vec<u8>,
},
PipeLine {
expressions: Vec1<Self>,
},
@ -344,7 +440,7 @@ impl UntypedExpr {
| Self::When { location, .. }
| Self::Call { location, .. }
| Self::List { location, .. }
| Self::Float { location, .. }
| Self::ByteArray { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }

View File

@ -1,11 +1,18 @@
use std::{cell::RefCell, collections::HashMap, sync::Arc};
use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc};
use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant};
use crate::{
ast::{Constant, ModuleKind, Span, TypedConstant},
tipo::fields::FieldMap,
};
use self::environment::Environment;
mod environment;
pub mod error;
mod expr;
pub mod fields;
mod hydrator;
pub mod infer;
mod infer;
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
@ -41,6 +48,161 @@ pub enum Type {
// Tuple { elems: Vec<Arc<Type>> },
}
impl Type {
pub fn is_result_constructor(&self) -> bool {
match self {
Type::Fn { ret, .. } => ret.is_result(),
_ => false,
}
}
pub fn is_result(&self) -> bool {
matches!(self, Self::App { name, module, .. } if "Result" == name && module.is_empty())
}
pub fn is_unbound(&self) -> bool {
matches!(self, Self::Var { tipo } if tipo.borrow().is_unbound())
}
pub fn return_type(&self) -> Option<Arc<Self>> {
match self {
Self::Fn { ret, .. } => Some(ret.clone()),
_ => None,
}
}
pub fn function_types(&self) -> Option<(Vec<Arc<Self>>, Arc<Self>)> {
match self {
Self::Fn { args, ret, .. } => Some((args.clone(), ret.clone())),
_ => None,
}
}
pub fn is_nil(&self) -> bool {
match self {
Self::App { module, name, .. } if "Nil" == name && module.is_empty() => true,
Self::Var { tipo } => tipo.borrow().is_nil(),
_ => false,
}
}
pub fn is_bool(&self) -> bool {
match self {
Self::App { module, name, .. } if "Bool" == name && module.is_empty() => true,
Self::Var { tipo } => tipo.borrow().is_bool(),
_ => false,
}
}
pub fn is_int(&self) -> bool {
match self {
Self::App { module, name, .. } if "Int" == name && module.is_empty() => true,
Self::Var { tipo } => tipo.borrow().is_int(),
_ => false,
}
}
pub fn is_bytearray(&self) -> bool {
match self {
Self::App { module, name, .. } if "ByteArray" == name && module.is_empty() => true,
Self::Var { tipo } => tipo.borrow().is_bytearray(),
_ => false,
}
}
pub fn is_string(&self) -> bool {
match self {
Self::App { module, name, .. } if "String" == name && module.is_empty() => true,
Self::Var { tipo } => tipo.borrow().is_string(),
_ => false,
}
}
/// Get the args for the type if the type is a specific `Type::App`.
/// Returns None if the type is not a `Type::App` or is an incorrect `Type:App`
///
/// This function is currently only used for finding the `List` type.
pub fn get_app_args(
&self,
public: bool,
module: &String,
name: &str,
arity: usize,
environment: &mut Environment<'_>,
) -> Option<Vec<Arc<Self>>> {
match self {
Self::App {
module: m,
name: n,
args,
..
} => {
if module == m && name == n && args.len() == arity {
Some(args.clone())
} else {
None
}
}
Self::Var { tipo } => {
let args: Vec<_> = match tipo.borrow().deref() {
TypeVar::Link { tipo } => {
return tipo.get_app_args(public, module, name, arity, environment);
}
TypeVar::Unbound { .. } => {
(0..arity).map(|_| environment.new_unbound_var()).collect()
}
TypeVar::Generic { .. } => return None,
};
// We are an unbound type variable! So convert us to a type link
// to the desired type.
*tipo.borrow_mut() = TypeVar::Link {
tipo: Arc::new(Self::App {
name: name.to_string(),
module: module.to_owned(),
args: args.clone(),
public,
}),
};
Some(args)
}
_ => None,
}
}
pub fn find_private_type(&self) -> Option<Self> {
match self {
Self::App { public: false, .. } => Some(self.clone()),
Self::App { args, .. } => args.iter().find_map(|t| t.find_private_type()),
// Self::Tuple { elems, .. } => elems.iter().find_map(|t| t.find_private_type()),
Self::Fn { ret, args, .. } => ret
.find_private_type()
.or_else(|| args.iter().find_map(|t| t.find_private_type())),
Self::Var { tipo, .. } => match tipo.borrow().deref() {
TypeVar::Unbound { .. } => None,
TypeVar::Generic { .. } => None,
TypeVar::Link { tipo, .. } => tipo.find_private_type(),
},
}
}
pub fn fn_arity(&self) -> Option<usize> {
match self {
Self::Fn { args, .. } => Some(args.len()),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeVar {
/// Unbound is an unbound variable. It is one specific type but we don't
@ -72,6 +234,41 @@ impl TypeVar {
pub fn is_unbound(&self) -> bool {
matches!(self, Self::Unbound { .. })
}
pub fn is_nil(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_nil(),
_ => false,
}
}
pub fn is_bool(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_bool(),
_ => false,
}
}
pub fn is_int(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_int(),
_ => false,
}
}
pub fn is_bytearray(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_bytearray(),
_ => false,
}
}
pub fn is_string(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_string(),
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
@ -89,6 +286,14 @@ impl ValueConstructor {
tipo,
}
}
fn field_map(&self) -> Option<&FieldMap> {
match &self.variant {
ValueConstructorVariant::ModuleFn { field_map, .. }
| ValueConstructorVariant::Record { field_map, .. } => field_map.as_ref(),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
@ -123,9 +328,20 @@ pub enum ValueConstructorVariant {
},
}
impl ValueConstructorVariant {
pub fn location(&self) -> Span {
match self {
ValueConstructorVariant::LocalVariable { location }
| ValueConstructorVariant::ModuleConstant { location, .. }
| ValueConstructorVariant::ModuleFn { location, .. }
| ValueConstructorVariant::Record { location, .. } => *location,
}
}
}
#[derive(Debug, Clone)]
pub struct Module {
pub name: Vec<String>,
pub struct TypeInfo {
pub name: String,
pub kind: ModuleKind,
pub package: String,
pub types: HashMap<String, TypeConstructor>,
@ -171,7 +387,7 @@ pub enum ModuleValueConstructor {
Record {
name: String,
arity: usize,
type_: Arc<Type>,
tipo: Arc<Type>,
field_map: Option<FieldMap>,
location: Span,
},

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use miette::Diagnostic;
@ -6,8 +6,19 @@ use crate::ast::{BinOp, Span, TodoKind};
use super::Type;
// use aiken/pub
// pub fn do_thing() { pub.other() }
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error {
#[error("duplicate argument {label}")]
DuplicateArgument {
#[label]
location: Span,
label: String,
},
#[error("duplicate const {name}")]
DuplicateConstName {
#[label]
@ -49,6 +60,15 @@ pub enum Error {
name: String,
},
#[error("incorrect arity expected {expected} but given {given}")]
IncorrectArity {
#[label]
location: Span,
expected: usize,
given: usize,
labels: Vec<String>,
},
#[error("{name} has incorrect type arity expected {expected} but given {given}")]
IncorrectTypeArity {
location: Span,
@ -57,18 +77,59 @@ pub enum Error {
given: usize,
},
#[error("not a function")]
NotFn {
#[label]
location: Span,
tipo: Arc<Type>,
},
#[error("{name} contains keyword {keyword}")]
KeywordInModuleName { name: String, keyword: String },
#[error("clause guard {name} is not local")]
NonLocalClauseGuardVariable {
#[label]
location: Span,
name: String,
},
#[error("positional argument after labeled")]
PositionalArgumentAfterLabeled {
#[label]
location: Span,
},
#[error("private type leaked")]
PrivateTypeLeak {
#[label]
location: Span,
leaked: Type,
},
#[error("{name} is a reserved module name")]
ReservedModuleName { name: String },
#[error("unexpected labeled argument {label}")]
UnexpectedLabeledArg {
#[label]
location: Span,
label: String,
},
#[error("unexpected type hole")]
UnexpectedTypeHole {
#[label]
location: Span,
},
#[error("unknown labels")]
UnknownLabels {
unknown: Vec<(String, Span)>,
valid: Vec<String>,
supplied: Vec<String>,
},
#[error("unknown module {name}")]
UnknownModule {
location: Span,
@ -85,54 +146,76 @@ pub enum Error {
type_constructors: Vec<String>,
},
#[error("")]
#[error("unknown module value {name}")]
UnknownModuleValue {
#[label]
location: Span,
name: String,
module_name: String,
value_constructors: Vec<String>,
},
#[error("unknown type {name} in module {module_name}")]
UnknownModuleType {
#[label]
location: Span,
name: String,
module_name: String,
type_constructors: Vec<String>,
},
#[error("unknown type {name}")]
UnknownType {
#[label]
location: Span,
name: String,
types: Vec<String>,
},
#[error("")]
UnknownTypeConstructorType {
#[error("unknown variable {name}")]
UnknownVariable {
#[label]
location: Span,
name: String,
type_constructors: Vec<String>,
},
#[error("")]
UnknownTypeConstructorModule {
location: Span,
name: String,
imported_modules: Vec<String>,
},
#[error("")]
UnknownTypeConstructorModuleType {
location: Span,
name: String,
module_name: Vec<String>,
type_constructors: Vec<String>,
variables: Vec<String>,
},
#[error("")]
CouldNotUnify {
#[label]
location: Span,
expected: Arc<Type>,
given: Arc<Type>,
situation: Option<UnifyErrorSituation>,
rigid_type_names: HashMap<u64, String>,
},
#[error("")]
ExtraVarInAlternativePattern { location: Span, name: String },
ExtraVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("")]
MissingVarInAlternativePattern { location: Span, name: String },
MissingVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("")]
DuplicateVarInPattern { location: Span, name: String },
DuplicateVarInPattern {
#[label]
location: Span,
name: String,
},
#[error("")]
RecursiveType { location: Span },
RecursiveType {
#[label]
location: Span,
},
}
impl Error {
@ -143,15 +226,53 @@ impl Error {
expected,
given,
situation: note,
rigid_type_names,
} => Error::CouldNotUnify {
location,
expected: given,
given: expected,
situation: note,
rigid_type_names,
},
other => other,
}
}
pub fn with_unify_error_rigid_names(mut self, new_names: &HashMap<u64, String>) -> Self {
match self {
Error::CouldNotUnify {
rigid_type_names: ref mut annotated_names,
..
} => {
*annotated_names = new_names.clone();
self
}
_ => self,
}
}
pub fn with_unify_error_situation(self, situation: UnifyErrorSituation) -> Self {
match self {
Self::CouldNotUnify {
expected,
given,
location,
rigid_type_names,
..
} => Self::CouldNotUnify {
expected,
given,
situation: Some(situation),
location,
rigid_type_names,
},
other => other,
}
}
pub fn return_annotation_mismatch(self) -> Self {
self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch)
}
}
#[derive(Debug, PartialEq, Clone)]

View File

@ -0,0 +1,597 @@
use std::{collections::HashMap, sync::Arc};
use crate::{
ast::{Annotation, ArgName, CallArg, Constant, Span, TypedArg, TypedConstant, UntypedConstant},
builtins::list,
expr::{TypedExpr, UntypedExpr},
tipo::fields::FieldMap,
};
use super::{
environment::{assert_no_labeled_arguments, EntityKind, Environment},
error::Error,
hydrator::Hydrator,
ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant,
};
#[derive(Debug)]
pub(crate) struct ExprTyper<'a, 'b> {
pub(crate) environment: &'a mut Environment<'b>,
// Type hydrator for creating types from annotations
pub(crate) hydrator: Hydrator,
// We keep track of whether any ungeneralised functions have been used
// to determine whether it is safe to generalise this expression after
// it has been inferred.
pub(crate) ungeneralised_function_used: bool,
}
impl<'a, 'b> ExprTyper<'a, 'b> {
fn get_field_map(
&mut self,
constructor: &TypedExpr,
location: Span,
) -> Result<Option<&FieldMap>, Error> {
let (module, name) = match constructor {
TypedExpr::ModuleSelect {
module_alias,
label,
..
} => (Some(module_alias), label),
TypedExpr::Var { name, .. } => (None, name),
_ => return Ok(None),
};
Ok(self
.environment
.get_value_constructor(module, name, location)?
.field_map())
}
pub fn in_new_scope<T>(&mut self, process_scope: impl FnOnce(&mut Self) -> T) -> T {
// Create new scope
let environment_reset_data = self.environment.open_new_scope();
let hydrator_reset_data = self.hydrator.open_new_scope();
// Process the scope
let result = process_scope(self);
// Close scope, discarding any scope local state
self.environment.close_scope(environment_reset_data);
self.hydrator.close_scope(hydrator_reset_data);
result
}
pub fn infer_fn_with_known_types(
&mut self,
args: Vec<TypedArg>,
body: UntypedExpr,
return_type: Option<Arc<Type>>,
) -> Result<(Vec<TypedArg>, TypedExpr), Error> {
let (body_rigid_names, body_infer) = self.in_new_scope(|body_typer| {
for (arg, t) in args.iter().zip(args.iter().map(|arg| arg.tipo.clone())) {
match &arg.arg_name {
ArgName::Named { name, .. } | ArgName::NamedLabeled { name, .. } => {
body_typer.environment.insert_variable(
name.to_string(),
ValueConstructorVariant::LocalVariable {
location: arg.location,
},
t,
);
body_typer.environment.init_usage(
name.to_string(),
EntityKind::Variable,
arg.location,
);
}
ArgName::Discard { .. } | ArgName::LabeledDiscard { .. } => (),
};
}
(body_typer.hydrator.rigid_names(), body_typer.infer(body))
});
let body = body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names))?;
// Check that any return type is accurate.
if let Some(return_type) = return_type {
self.unify(return_type, body.tipo(), body.type_defining_location())
.map_err(|e| {
e.return_annotation_mismatch()
.with_unify_error_rigid_names(&body_rigid_names)
})?;
}
Ok((args, body))
}
/// Crawl the AST, annotating each node with the inferred type or
/// returning an error.
///
pub fn infer(&mut self, expr: UntypedExpr) -> Result<TypedExpr, Error> {
match expr {
UntypedExpr::Todo {
location,
label,
kind,
..
} => Ok(self.infer_todo(location, kind, label)),
UntypedExpr::Var { location, name, .. } => self.infer_var(name, location),
UntypedExpr::Int {
location, value, ..
} => Ok(self.infer_int(value, location)),
UntypedExpr::Sequence {
expressions,
location,
} => self.infer_seq(location, expressions),
UntypedExpr::Tuple {
location, elems, ..
} => self.infer_tuple(elems, location),
UntypedExpr::String {
location, value, ..
} => Ok(self.infer_string(value, location)),
UntypedExpr::PipeLine { expressions } => self.infer_pipeline(expressions),
UntypedExpr::Fn {
location,
is_capture,
arguments: args,
body,
return_annotation,
..
} => self.infer_fn(args, &[], *body, is_capture, return_annotation, location),
UntypedExpr::Assignment {
location,
pattern,
value,
kind,
annotation,
..
} => self.infer_assignment(pattern, *value, kind, &annotation, location),
UntypedExpr::Try {
location,
pattern,
value,
then,
annotation,
..
} => self.infer_try(pattern, *value, *then, &annotation, location),
UntypedExpr::Case {
location,
subjects,
clauses,
..
} => self.infer_case(subjects, clauses, location),
UntypedExpr::List {
location,
elements,
tail,
..
} => self.infer_list(elements, tail, location),
UntypedExpr::Call {
location,
fun,
arguments: args,
..
} => self.infer_call(*fun, args, location),
UntypedExpr::BinOp {
location,
name,
left,
right,
..
} => self.infer_binop(name, *left, *right, location),
UntypedExpr::FieldAccess {
location,
label,
container,
..
} => self.infer_field_access(*container, label, location),
UntypedExpr::TupleIndex {
location,
index,
tuple,
..
} => self.infer_tuple_index(*tuple, index, location),
UntypedExpr::BitString { location, segments } => {
self.infer_bit_string(segments, location)
}
UntypedExpr::RecordUpdate {
location,
constructor,
spread,
arguments: args,
} => self.infer_record_update(*constructor, spread, args, location),
UntypedExpr::Negate { location, value } => self.infer_negate(location, value),
}
}
pub fn type_from_annotation(&mut self, annotation: &Annotation) -> Result<Arc<Type>, Error> {
self.hydrator
.type_from_annotation(annotation, self.environment)
}
// TODO: extract the type annotation checking into a infer_module_const
// function that uses this function internally
pub fn infer_const(
&mut self,
annotation: &Option<Annotation>,
value: UntypedConstant,
) -> Result<TypedConstant, Error> {
let inferred = match value {
Constant::Int {
location, value, ..
} => Ok(Constant::Int { location, value }),
Constant::String {
location, value, ..
} => Ok(Constant::String { location, value }),
Constant::List {
elements, location, ..
} => self.infer_const_list(elements, location),
Constant::ByteArray { location, bytes } => Ok(Constant::ByteArray { location, bytes }),
Constant::Record {
module,
location,
name,
args,
// field_map, is always None here because untyped not yet unified
..
} if args.is_empty() => {
// Register the module as having been used if it was imported
if let Some(ref module) = &module {
self.environment.unused_modules.remove(module);
}
// Type check the record constructor
let constructor = self.infer_value_constructor(&module, &name, &location)?;
let (tag, field_map) = match &constructor.variant {
ValueConstructorVariant::Record {
name, field_map, ..
} => (name.clone(), field_map.clone()),
ValueConstructorVariant::ModuleFn { .. }
| ValueConstructorVariant::LocalVariable { .. } => {
return Err(Error::NonLocalClauseGuardVariable { location, name })
}
// TODO: remove this clone. Could use an rc instead
ValueConstructorVariant::ModuleConstant { literal, .. } => {
return Ok(literal.clone())
}
};
Ok(Constant::Record {
module,
location,
name,
args: vec![],
tipo: constructor.tipo,
tag,
field_map,
})
}
Constant::Record {
module,
location,
name,
mut args,
// field_map, is always None here because untyped not yet unified
..
} => {
// Register the module as having been used if it was imported
if let Some(ref module) = &module {
self.environment.unused_modules.remove(module);
}
let constructor = self.infer_value_constructor(&module, &name, &location)?;
let (tag, field_map) = match &constructor.variant {
ValueConstructorVariant::Record {
name, field_map, ..
} => (name.clone(), field_map.clone()),
ValueConstructorVariant::ModuleFn { .. }
| ValueConstructorVariant::LocalVariable { .. } => {
return Err(Error::NonLocalClauseGuardVariable { location, name })
}
// TODO: remove this clone. Could be an rc instead
ValueConstructorVariant::ModuleConstant { literal, .. } => {
return Ok(literal.clone())
}
};
// Pretty much all the other infer functions operate on UntypedExpr
// or TypedExpr rather than ClauseGuard. To make things easier we
// build the TypedExpr equivalent of the constructor and use that
// TODO: resvisit this. It is rather awkward at present how we
// have to convert to this other data structure.
let fun = match &module {
Some(module_name) => {
let tipo = Arc::clone(&constructor.tipo);
let module_name = self
.environment
.imported_modules
.get(module_name)
.expect("Failed to find previously located module import")
.1
.name;
let module_value_constructor = ModuleValueConstructor::Record {
name: name.clone(),
field_map: field_map.clone(),
arity: args.len(),
tipo: Arc::clone(&tipo),
location: constructor.variant.location(),
};
TypedExpr::ModuleSelect {
label: name.clone(),
module_alias: module_name.clone(),
module_name,
tipo,
constructor: module_value_constructor,
location,
}
}
None => TypedExpr::Var {
constructor,
location,
name: name.clone(),
},
};
// This is basically the same code as do_infer_call_with_known_fun()
// except the args are typed with infer_clause_guard() here.
// This duplication is a bit awkward but it works!
// Potentially this could be improved later
match self.get_field_map(&fun, location)? {
// The fun has a field map so labelled arguments may be present and need to be reordered.
Some(field_map) => field_map.reorder(&mut args, location)?,
// The fun has no field map and so we error if arguments have been labelled
None => assert_no_labeled_arguments(&args)?,
}
let (mut args_types, return_type) = self.environment.match_fun_type(
fun.tipo(),
args.len(),
fun.location(),
location,
)?;
let mut typed_args = Vec::new();
for (tipo, arg) in args_types.iter_mut().zip(args) {
let CallArg {
label,
value,
location,
} = arg;
let value = self.infer_const(&None, value)?;
self.unify(tipo.clone(), value.tipo(), value.location())?;
typed_args.push(CallArg {
label,
value,
location,
});
}
Ok(Constant::Record {
module,
location,
name,
args: typed_args,
tipo: return_type,
tag,
field_map,
})
}
Constant::Var {
location,
module,
name,
..
} => {
// Register the module as having been used if it was imported
if let Some(ref module) = &module {
self.environment.unused_modules.remove(module);
}
// Infer the type of this constant
let constructor = self.infer_value_constructor(&module, &name, &location)?;
match constructor.variant {
ValueConstructorVariant::ModuleConstant { .. }
| ValueConstructorVariant::ModuleFn { .. } => Ok(Constant::Var {
location,
module,
name,
tipo: Arc::clone(&constructor.tipo),
constructor: Some(Box::from(constructor)),
}),
// constructor.variant cannot be a LocalVariable because module constants can
// only be defined at module scope. It also cannot be a Record because then
// this constant would have been parsed as a Constant::Record. Therefore this
// code is unreachable.
_ => unreachable!(),
}
}
}?;
// Check type annotation is accurate.
if let Some(ann) = annotation {
let const_ann = self.type_from_annotation(ann)?;
self.unify(const_ann, inferred.tipo(), inferred.location())?;
};
Ok(inferred)
}
fn infer_const_list(
&mut self,
untyped_elements: Vec<UntypedConstant>,
location: Span,
) -> Result<TypedConstant, Error> {
let tipo = self.new_unbound_var();
let mut elements = Vec::with_capacity(untyped_elements.len());
for element in untyped_elements {
let element = self.infer_const(&None, element)?;
self.unify(tipo.clone(), element.tipo(), element.location())?;
elements.push(element);
}
Ok(Constant::List {
elements,
location,
tipo: list(tipo),
})
}
fn infer_value_constructor(
&mut self,
module: &Option<String>,
name: &str,
location: &Span,
) -> Result<ValueConstructor, Error> {
let constructor = match module {
// Look in the current scope for a binding with this name
None => {
let constructor =
self.environment
.get_variable(name)
.cloned()
.ok_or_else(|| Error::UnknownVariable {
location: *location,
name: name.to_string(),
variables: self.environment.local_value_names(),
})?;
// Note whether we are using an ungeneralised function so that we can
// tell if it is safe to generalise this function after inference has
// completed.
if matches!(
&constructor.variant,
ValueConstructorVariant::ModuleFn { .. }
) {
let is_ungeneralised = self.environment.ungeneralised_functions.contains(name);
self.ungeneralised_function_used =
self.ungeneralised_function_used || is_ungeneralised;
}
// Register the value as seen for detection of unused values
self.environment.increment_usage(name);
constructor
}
// Look in an imported module for a binding with this name
Some(module_name) => {
let (_, module) = &self
.environment
.imported_modules
.get(module_name)
.ok_or_else(|| Error::UnknownModule {
location: *location,
name: module_name.to_string(),
imported_modules: self
.environment
.imported_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
module
.values
.get(name)
.cloned()
.ok_or_else(|| Error::UnknownModuleValue {
location: *location,
module_name: module_name.to_string(),
name: name.to_string(),
value_constructors: module.values.keys().map(|t| t.to_string()).collect(),
})?
}
};
let ValueConstructor {
public,
variant,
tipo,
} = constructor;
// Instantiate generic variables into unbound variables for this usage
let tipo = self.instantiate(tipo, &mut HashMap::new());
Ok(ValueConstructor {
public,
variant,
tipo,
})
}
fn instantiate(&mut self, t: Arc<Type>, ids: &mut HashMap<u64, Arc<Type>>) -> Arc<Type> {
self.environment.instantiate(t, ids, &self.hydrator)
}
pub fn new(environment: &'a mut Environment<'b>) -> Self {
let mut hydrator = Hydrator::new();
hydrator.permit_holes(true);
Self {
hydrator,
environment,
ungeneralised_function_used: false,
}
}
pub fn new_unbound_var(&mut self) -> Arc<Type> {
self.environment.new_unbound_var()
}
fn unify(&mut self, t1: Arc<Type>, t2: Arc<Type>, location: Span) -> Result<(), Error> {
self.environment.unify(t1, t2, location)
}
}

View File

@ -0,0 +1,147 @@
use std::collections::{HashMap, HashSet};
use itertools::Itertools;
use super::error::Error;
use crate::ast::{CallArg, Span};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldMap {
pub arity: usize,
pub fields: HashMap<String, usize>,
}
impl FieldMap {
pub fn new(arity: usize) -> Self {
Self {
arity,
fields: HashMap::new(),
}
}
pub fn insert(&mut self, label: String, index: usize, location: &Span) -> Result<(), Error> {
match self.fields.insert(label.clone(), index) {
Some(_) => Err(Error::DuplicateField {
label,
location: *location,
}),
None => Ok(()),
}
}
pub fn into_option(self) -> Option<Self> {
if self.fields.is_empty() {
None
} else {
Some(self)
}
}
/// Reorder an argument list so that labelled fields supplied out-of-order are
/// in the correct order.
pub fn reorder<A>(&self, args: &mut [CallArg<A>], location: Span) -> Result<(), Error> {
let mut labeled_arguments_given = false;
let mut seen_labels = std::collections::HashSet::new();
let mut unknown_labels = Vec::new();
if self.arity as usize != args.len() {
return Err(Error::IncorrectArity {
labels: self.incorrect_arity_labels(args),
location,
expected: self.arity as usize,
given: args.len(),
});
}
for arg in args.iter() {
match &arg.label {
Some(_) => {
labeled_arguments_given = true;
}
None => {
if labeled_arguments_given {
return Err(Error::PositionalArgumentAfterLabeled {
location: arg.location,
});
}
}
}
}
let mut i = 0;
while i < args.len() {
let label = &args.get(i).expect("Field indexing to get label").label;
let (label, &location) = match label {
// A labelled argument, we may need to reposition it in the array vector
Some(l) => (
l,
&args
.get(i)
.expect("Indexing in labelled field reordering")
.location,
),
// Not a labelled argument
None => {
i += 1;
continue;
}
};
let position = match self.fields.get(label) {
None => {
unknown_labels.push((label.clone(), location));
i += 1;
continue;
}
Some(&p) => p,
};
// If the argument is already in the right place
if position as usize == i {
seen_labels.insert(label.clone());
i += 1;
} else {
if seen_labels.contains(label) {
return Err(Error::DuplicateArgument {
location,
label: label.to_string(),
});
}
seen_labels.insert(label.clone());
args.swap(position as usize, i);
}
}
if unknown_labels.is_empty() {
Ok(())
} else {
let valid = self.fields.keys().map(|t| t.to_string()).collect();
Err(Error::UnknownLabels {
valid,
unknown: unknown_labels,
supplied: seen_labels.into_iter().collect(),
})
}
}
pub fn incorrect_arity_labels<A>(&self, args: &[CallArg<A>]) -> Vec<String> {
let given: HashSet<_> = args.iter().filter_map(|arg| arg.label.as_ref()).collect();
self.fields
.keys()
.cloned()
.filter(|f| !given.contains(f))
.sorted()
.collect()
}
}

View File

@ -1,54 +1,423 @@
use std::collections::HashMap;
use crate::{
ast::{ModuleKind, TypedModule, UntypedModule},
ast::{
Definition, Layer, ModuleKind, RecordConstructor, RecordConstructorArg, TypedDefinition,
TypedModule, UntypedDefinition, UntypedModule,
},
builtins::function,
token::Token,
IdGenerator,
};
use super::{
environment::Environment,
environment::{generalise, EntityKind, Environment},
error::{Error, Warning},
Module,
expr::ExprTyper,
hydrator::Hydrator,
TypeInfo, ValueConstructor, ValueConstructorVariant,
};
pub fn module(
id_gen: &IdGenerator,
mut module: UntypedModule,
kind: ModuleKind,
package: &str,
modules: &HashMap<String, Module>,
warnings: &mut Vec<Warning>,
) -> Result<TypedModule, Error> {
let name = module.name.clone();
let docs = std::mem::take(&mut module.docs);
let mut environment = Environment::new(id_gen.clone(), &name, modules, warnings);
impl UntypedModule {
pub fn infer(
mut self,
id_gen: &IdGenerator,
kind: ModuleKind,
package: &str,
modules: &HashMap<String, TypeInfo>,
warnings: &mut Vec<Warning>,
) -> Result<TypedModule, Error> {
let name = self.name.clone();
let docs = std::mem::take(&mut self.docs);
let mut environment = Environment::new(id_gen.clone(), &name, modules, warnings);
validate_module_name(&name)?;
validate_module_name(&name)?;
let mut type_names = HashMap::with_capacity(module.definitions.len());
let mut value_names = HashMap::with_capacity(module.definitions.len());
let mut hydrators = HashMap::with_capacity(module.definitions.len());
let mut type_names = HashMap::with_capacity(self.definitions.len());
let mut value_names = HashMap::with_capacity(self.definitions.len());
let mut hydrators = HashMap::with_capacity(self.definitions.len());
// Register any modules, types, and values being imported
// We process imports first so that anything imported can be referenced
// anywhere in the module.
for def in module.definitions() {
environment.register_import(def)?;
// Register any modules, types, and values being imported
// We process imports first so that anything imported can be referenced
// anywhere in the module.
for def in self.definitions() {
environment.register_import(def)?;
}
// Register types so they can be used in constructors and functions
// earlier in the module.
for def in self.definitions() {
environment.register_types(def, &name, &mut hydrators, &mut type_names)?;
}
// Register values so they can be used in functions earlier in the module.
for def in self.definitions() {
environment.register_values(def, &name, &mut hydrators, &mut value_names)?;
}
// Infer the types of each definition in the module
// We first infer all the constants so they can be used in functions defined
// anywhere in the module.
let mut definitions = Vec::with_capacity(self.definitions.len());
let mut consts = vec![];
let mut not_consts = vec![];
for def in self.into_definitions() {
match def {
Definition::ModuleConstant { .. } => consts.push(def),
Definition::Fn { .. }
| Definition::TypeAlias { .. }
| Definition::DataType { .. }
| Definition::Use { .. } => not_consts.push(def),
}
}
for def in consts.into_iter().chain(not_consts) {
let definition = infer_definition(def, &name, &mut hydrators, &mut environment)?;
definitions.push(definition);
}
// Generalise functions now that the entire module has been inferred
let definitions = definitions
.into_iter()
.map(|def| environment.generalise_definition(def, &name))
.collect();
// Generate warnings for unused items
environment.convert_unused_to_warnings();
// Remove private and imported types and values to create the public interface
environment
.module_types
.retain(|_, info| info.public && info.module == name);
environment.module_values.retain(|_, info| info.public);
environment
.accessors
.retain(|_, accessors| accessors.public);
// Ensure no exported values have private types in their type signature
for value in environment.module_values.values() {
if let Some(leaked) = value.tipo.find_private_type() {
return Err(Error::PrivateTypeLeak {
location: value.variant.location(),
leaked,
});
}
}
let Environment {
module_types: types,
module_types_constructors: types_constructors,
module_values: values,
accessors,
..
} = environment;
Ok(TypedModule {
docs,
name: name.clone(),
definitions,
kind,
type_info: TypeInfo {
name,
types,
types_constructors,
values,
accessors,
kind,
package: package.to_string(),
},
})
}
}
// Register types so they can be used in constructors and functions
// earlier in the module.
for def in module.definitions() {
environment.register_types(def, &name, &mut hydrators, &mut type_names)?;
fn infer_definition(
def: UntypedDefinition,
module_name: &String,
hydrators: &mut HashMap<String, Hydrator>,
environment: &mut Environment<'_>,
) -> Result<TypedDefinition, Error> {
match def {
Definition::Fn {
doc,
location,
name,
public,
arguments: args,
body,
return_annotation,
..
} => {
let preregistered_fn = environment
.get_variable(&name)
.expect("Could not find preregistered type for function");
let field_map = preregistered_fn.field_map().cloned();
let preregistered_type = preregistered_fn.tipo.clone();
let (args_types, return_type) = preregistered_type
.function_types()
.expect("Preregistered type for fn was not a fn");
// Infer the type using the preregistered args + return types as a starting point
let (tipo, args, body, safe_to_generalise) =
environment.in_new_scope(|environment| {
let args = args
.into_iter()
.zip(&args_types)
.map(|(arg_name, tipo)| arg_name.set_type(tipo.clone()))
.collect();
let mut expr_typer = ExprTyper::new(environment);
expr_typer.hydrator = hydrators
.remove(&name)
.expect("Could not find hydrator for fn");
let (args, body) =
expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?;
let args_types = args.iter().map(|a| a.tipo.clone()).collect();
let tipo = function(args_types, body.tipo());
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
Ok((tipo, args, body, safe_to_generalise))
})?;
// Assert that the inferred type matches the type of any recursive call
environment.unify(preregistered_type, tipo.clone(), location)?;
// Generalise the function if safe to do so
let tipo = if safe_to_generalise {
environment.ungeneralised_functions.remove(&name);
let tipo = generalise(tipo, 0);
let module_fn = ValueConstructorVariant::ModuleFn {
name: name.clone(),
field_map,
module: module_name.to_owned(),
arity: args.len(),
location,
};
environment.insert_variable(name.clone(), module_fn, tipo.clone());
tipo
} else {
tipo
};
Ok(Definition::Fn {
doc,
location,
name,
public,
arguments: args,
return_annotation,
return_type: tipo
.return_type()
.expect("Could not find return type for fn"),
body,
})
}
Definition::TypeAlias {
doc,
location,
public,
alias,
parameters,
annotation,
..
} => {
let tipo = environment
.get_type_constructor(&None, &alias, location)
.expect("Could not find existing type for type alias")
.tipo
.clone();
Ok(Definition::TypeAlias {
doc,
location,
public,
alias,
parameters,
annotation,
tipo,
})
}
Definition::DataType {
doc,
location,
public,
opaque,
name,
parameters,
constructors,
..
} => {
let constructors = constructors
.into_iter()
.map(
|RecordConstructor {
location,
name,
arguments: args,
documentation,
sugar,
}| {
let preregistered_fn = environment
.get_variable(&name)
.expect("Could not find preregistered type for function");
let preregistered_type = preregistered_fn.tipo.clone();
let args = if let Some((args_types, _return_type)) =
preregistered_type.function_types()
{
args.into_iter()
.zip(&args_types)
.map(
|(
RecordConstructorArg {
label,
annotation,
location,
..
},
t,
)| {
RecordConstructorArg {
label,
annotation,
location,
tipo: t.clone(),
doc: None,
}
},
)
.collect()
} else {
vec![]
};
RecordConstructor {
location,
name,
arguments: args,
documentation,
sugar,
}
},
)
.collect();
let typed_parameters = environment
.get_type_constructor(&None, &name, location)
.expect("Could not find preregistered type constructor ")
.parameters
.clone();
Ok(Definition::DataType {
doc,
location,
public,
opaque,
name,
parameters,
constructors,
typed_parameters,
})
}
Definition::Use {
location,
module,
as_name,
mut unqualified,
..
} => {
let name = module.join("/");
// Find imported module
let module_info =
environment
.importable_modules
.get(&name)
.ok_or_else(|| Error::UnknownModule {
location,
name,
imported_modules: environment.imported_modules.keys().cloned().collect(),
})?;
// TODO: remove this most likely
// Record any imports that are types only as this information is
// needed to prevent types being imported in generated JavaScript
for import in unqualified.iter_mut() {
if environment.imported_types.contains(import.variable_name()) {
import.layer = Layer::Type;
}
}
Ok(Definition::Use {
location,
module,
as_name,
unqualified,
package: module_info.package.clone(),
})
}
Definition::ModuleConstant {
doc,
location,
name,
annotation,
public,
value,
..
} => {
let typed_expr = ExprTyper::new(environment).infer_const(&annotation, *value)?;
let tipo = typed_expr.tipo();
let variant = ValueConstructor {
public,
variant: ValueConstructorVariant::ModuleConstant {
location,
literal: typed_expr.clone(),
module: module_name.to_owned(),
},
tipo: tipo.clone(),
};
environment.insert_variable(name.clone(), variant.variant.clone(), tipo.clone());
environment.insert_module_value(&name, variant);
if !public {
environment.init_usage(name.clone(), EntityKind::PrivateConstant, location);
}
Ok(Definition::ModuleConstant {
doc,
location,
name,
annotation,
public,
value: Box::new(typed_expr),
tipo,
})
}
}
// Register values so they can be used in functions earlier in the module.
for def in module.definitions() {
environment.register_values(def, &name, &mut hydrators, &mut value_names)?;
}
todo!()
}
fn validate_module_name(name: &str) -> Result<(), Error> {
@ -70,7 +439,7 @@ fn validate_module_name(name: &str) -> Result<(), Error> {
Ok(())
}
pub fn str_to_keyword(word: &str) -> Option<Token> {
fn str_to_keyword(word: &str) -> Option<Token> {
// Alphabetical keywords:
match word {
"as" => Some(Token::As),