feat: register import, types, and values in environment

This commit is contained in:
rvcas 2022-10-18 19:55:46 -04:00 committed by Lucas
parent d0287d418b
commit 81c87ab4da
11 changed files with 1773 additions and 38 deletions

View File

@ -45,6 +45,15 @@ pub enum Error {
}, },
} }
impl Error {
pub fn total(&self) -> usize {
match self {
Error::List(errors) => errors.len(),
_ => 1,
}
}
}
impl Debug for Error { impl Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let miette_handler = MietteHandlerOpts::new() let miette_handler = MietteHandlerOpts::new()

View File

@ -49,15 +49,23 @@ fn main() -> miette::Result<()> {
let mut project = Project::new(config, project_path); let mut project = Project::new(config, project_path);
if let Err(err) = project.build() { let build_result = project.build();
match err {
for warning in project.warnings {
eprintln!("Warning: {:?}", warning)
}
if let Err(err) = build_result {
match &err {
error::Error::List(errors) => { error::Error::List(errors) => {
for error in errors { for error in errors {
eprintln!("Error: {:?}", error) eprintln!("Error: {:?}", error)
} }
} }
rest => Err(rest)?, rest => eprintln!("Error: {:?}", rest),
} }
miette::bail!("failed: {} errors", err.total());
}; };
} }

View File

@ -42,7 +42,7 @@ pub struct Project {
module_types: HashMap<String, tipo::Module>, module_types: HashMap<String, tipo::Module>,
root: PathBuf, root: PathBuf,
sources: Vec<Source>, sources: Vec<Source>,
warnings: Vec<Warning>, pub warnings: Vec<Warning>,
} }
impl Project { impl Project {

View File

@ -181,6 +181,38 @@ pub struct FieldMap {
pub fields: HashMap<String, 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)] #[derive(Debug, Clone, PartialEq)]
pub struct RecordConstructor<T> { pub struct RecordConstructor<T> {
pub location: Span, pub location: Span,
@ -261,17 +293,84 @@ pub enum Annotation {
name: String, name: String,
}, },
Tuple {
location: Span,
elems: Vec<Self>,
},
Hole { Hole {
location: Span, location: Span,
name: String, name: String,
}, },
} }
impl Annotation {
pub fn location(&self) -> Span {
match self {
Annotation::Fn { location, .. }
| Annotation::Var { location, .. }
| Annotation::Hole { location, .. }
| Annotation::Constructor { location, .. } => *location,
}
}
pub fn is_logically_equal(&self, other: &Annotation) -> bool {
match self {
Annotation::Constructor {
module,
name,
arguments,
location: _,
} => match other {
Annotation::Constructor {
module: o_module,
name: o_name,
arguments: o_arguments,
location: _,
} => {
module == o_module
&& name == o_name
&& arguments.len() == o_arguments.len()
&& arguments
.iter()
.zip(o_arguments)
.all(|a| a.0.is_logically_equal(a.1))
}
_ => false,
},
Annotation::Fn {
arguments,
ret,
location: _,
} => match other {
Annotation::Fn {
arguments: o_arguments,
ret: o_return,
location: _,
} => {
arguments.len() == o_arguments.len()
&& arguments
.iter()
.zip(o_arguments)
.all(|a| a.0.is_logically_equal(a.1))
&& ret.is_logically_equal(o_return)
}
_ => false,
},
Annotation::Var { name, location: _ } => match other {
Annotation::Var {
name: o_name,
location: _,
} => name == o_name,
_ => false,
},
Annotation::Hole { name, location: _ } => match other {
Annotation::Hole {
name: o_name,
location: _,
} => name == o_name,
_ => false,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Layer { pub enum Layer {
Value, Value,
@ -284,7 +383,7 @@ impl Default for Layer {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp { pub enum BinOp {
// Boolean logic // Boolean logic
And, And,

View File

@ -34,7 +34,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
parameters: vec![], parameters: vec![],
tipo: int(), tipo: int(),
origin: Span::empty(), origin: Span::empty(),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -46,7 +46,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![], parameters: vec![],
tipo: byte_array(), tipo: byte_array(),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -93,7 +93,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![], parameters: vec![],
tipo: bool(), tipo: bool(),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -106,7 +106,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![list_parameter.clone()], parameters: vec![list_parameter.clone()],
tipo: list(list_parameter), tipo: list(list_parameter),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -118,7 +118,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![], parameters: vec![],
tipo: string(), tipo: string(),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -145,7 +145,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![], parameters: vec![],
tipo: nil(), tipo: nil(),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -160,7 +160,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module {
origin: Span::empty(), origin: Span::empty(),
parameters: vec![result_value.clone(), result_error.clone()], parameters: vec![result_value.clone(), result_error.clone()],
tipo: result(result_value, result_error), tipo: result(result_value, result_error),
module: vec![], module: "".to_string(),
public: true, public: true,
}, },
); );
@ -211,7 +211,7 @@ pub fn int() -> Arc<Type> {
Arc::new(Type::App { Arc::new(Type::App {
public: true, public: true,
name: INT.to_string(), name: INT.to_string(),
module: vec![], module: "".to_string(),
args: vec![], args: vec![],
}) })
} }
@ -221,7 +221,7 @@ pub fn byte_array() -> Arc<Type> {
args: vec![], args: vec![],
public: true, public: true,
name: BYTE_ARRAY.to_string(), name: BYTE_ARRAY.to_string(),
module: vec![], module: "".to_string(),
}) })
} }
@ -230,7 +230,7 @@ pub fn bool() -> Arc<Type> {
args: vec![], args: vec![],
public: true, public: true,
name: BOOL.to_string(), name: BOOL.to_string(),
module: vec![], module: "".to_string(),
}) })
} }
@ -238,7 +238,7 @@ pub fn list(t: Arc<Type>) -> Arc<Type> {
Arc::new(Type::App { Arc::new(Type::App {
public: true, public: true,
name: LIST.to_string(), name: LIST.to_string(),
module: vec![], module: "".to_string(),
args: vec![t], args: vec![t],
}) })
} }
@ -248,7 +248,7 @@ pub fn string() -> Arc<Type> {
args: vec![], args: vec![],
public: true, public: true,
name: STRING.to_string(), name: STRING.to_string(),
module: vec![], module: "".to_string(),
}) })
} }
@ -257,7 +257,7 @@ pub fn nil() -> Arc<Type> {
args: vec![], args: vec![],
public: true, public: true,
name: NIL.to_string(), name: NIL.to_string(),
module: vec![], module: "".to_string(),
}) })
} }
@ -265,7 +265,7 @@ pub fn result(a: Arc<Type>, e: Arc<Type>) -> Arc<Type> {
Arc::new(Type::App { Arc::new(Type::App {
public: true, public: true,
name: RESULT.to_string(), name: RESULT.to_string(),
module: vec![], module: "".to_string(),
args: vec![a, e], args: vec![a, e],
}) })
} }
@ -279,3 +279,9 @@ pub fn generic_var(id: u64) -> Arc<Type> {
Arc::new(Type::Var { tipo }) Arc::new(Type::Var { tipo })
} }
pub fn unbound_var(id: u64) -> Arc<Type> {
let tipo = Arc::new(RefCell::new(TypeVar::Unbound { id }));
Arc::new(Type::Var { tipo })
}

View File

@ -1,4 +1,7 @@
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
pub mod ast; pub mod ast;
pub mod builtins; pub mod builtins;
@ -9,9 +12,9 @@ pub mod parser;
pub mod tipo; pub mod tipo;
pub mod token; pub mod token;
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct IdGenerator { pub struct IdGenerator {
id: AtomicU64, id: Arc<AtomicU64>,
} }
impl IdGenerator { impl IdGenerator {

View File

@ -2,7 +2,9 @@ use std::{cell::RefCell, collections::HashMap, sync::Arc};
use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant}; use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant};
mod environment;
pub mod error; pub mod error;
mod hydrator;
pub mod infer; pub mod infer;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -17,7 +19,7 @@ pub enum Type {
/// ///
App { App {
public: bool, public: bool,
module: Vec<String>, module: String,
name: String, name: String,
args: Vec<Arc<Type>>, args: Vec<Arc<Type>>,
}, },
@ -32,12 +34,11 @@ pub enum Type {
/// A type variable. See the contained `TypeVar` enum for more information. /// A type variable. See the contained `TypeVar` enum for more information.
/// ///
Var { tipo: Arc<RefCell<TypeVar>> }, Var { tipo: Arc<RefCell<TypeVar>> },
// /// A tuple is an ordered collection of 0 or more values, each of which
/// A tuple is an ordered collection of 0 or more values, each of which // /// can have a different type, so the `tuple` type is the sum of all the
/// can have a different type, so the `tuple` type is the sum of all the // /// contained types.
/// contained types. // ///
/// // Tuple { elems: Vec<Arc<Type>> },
Tuple { elems: Vec<Arc<Type>> },
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -67,6 +68,12 @@ pub enum TypeVar {
Generic { id: u64 }, Generic { id: u64 },
} }
impl TypeVar {
pub fn is_unbound(&self) -> bool {
matches!(self, Self::Unbound { .. })
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ValueConstructor { pub struct ValueConstructor {
pub public: bool, pub public: bool,
@ -100,7 +107,7 @@ pub enum ValueConstructorVariant {
ModuleFn { ModuleFn {
name: String, name: String,
field_map: Option<FieldMap>, field_map: Option<FieldMap>,
module: Vec<String>, module: String,
arity: usize, arity: usize,
location: Span, location: Span,
}, },
@ -131,7 +138,7 @@ pub struct Module {
pub struct TypeConstructor { pub struct TypeConstructor {
pub public: bool, pub public: bool,
pub origin: Span, pub origin: Span,
pub module: Vec<String>, pub module: String,
pub parameters: Vec<Arc<Type>>, pub parameters: Vec<Arc<Type>>,
pub tipo: Arc<Type>, pub tipo: Arc<Type>,
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,157 @@ use std::sync::Arc;
use miette::Diagnostic; use miette::Diagnostic;
use crate::ast::{Span, TodoKind}; use crate::ast::{BinOp, Span, TodoKind};
use super::Type; use super::Type;
#[derive(Debug, thiserror::Error, Diagnostic)] #[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error {} pub enum Error {
#[error("duplicate const {name}")]
DuplicateConstName {
#[label]
location: Span,
#[label]
previous_location: Span,
name: String,
},
#[error("duplicate import {name}")]
DuplicateImport {
#[label]
location: Span,
#[label]
previous_location: Span,
name: String,
},
#[error("duplicate name {label}")]
DuplicateField {
#[label]
location: Span,
label: String,
},
#[error("duplicate name {name}")]
DuplicateName {
#[label]
location: Span,
#[label]
previous_location: Span,
name: String,
},
#[error("duplicate type name {name}")]
DuplicateTypeName {
location: Span,
previous_location: Span,
name: String,
},
#[error("{name} has incorrect type arity expected {expected} but given {given}")]
IncorrectTypeArity {
location: Span,
name: String,
expected: usize,
given: usize,
},
#[error("{name} contains keyword {keyword}")]
KeywordInModuleName { name: String, keyword: String },
#[error("{name} is a reserved module name")]
ReservedModuleName { name: String },
#[error("unexpected type hole")]
UnexpectedTypeHole {
#[label]
location: Span,
},
#[error("unknown module {name}")]
UnknownModule {
location: Span,
name: String,
imported_modules: Vec<String>,
},
#[error("unknown module field {name} in module {module_name}")]
UnknownModuleField {
location: Span,
name: String,
module_name: String,
value_constructors: Vec<String>,
type_constructors: Vec<String>,
},
#[error("")]
UnknownType {
location: Span,
name: String,
types: Vec<String>,
},
#[error("")]
UnknownTypeConstructorType {
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>,
},
#[error("")]
CouldNotUnify {
location: Span,
expected: Arc<Type>,
given: Arc<Type>,
situation: Option<UnifyErrorSituation>,
},
#[error("")]
ExtraVarInAlternativePattern { location: Span, name: String },
#[error("")]
MissingVarInAlternativePattern { location: Span, name: String },
#[error("")]
DuplicateVarInPattern { location: Span, name: String },
#[error("")]
RecursiveType { location: Span },
}
impl Error {
pub fn flip_unify(self) -> Error {
match self {
Error::CouldNotUnify {
location,
expected,
given,
situation: note,
} => Error::CouldNotUnify {
location,
expected: given,
given: expected,
situation: note,
},
other => other,
}
}
}
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Warning { pub enum Warning {
@ -70,3 +215,24 @@ pub enum Warning {
name: String, name: String,
}, },
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnifyErrorSituation {
/// Clauses in a case expression were found to return different types.
CaseClauseMismatch,
/// A function was found to return a value that did not match its return
/// annotation.
ReturnAnnotationMismatch,
PipeTypeMismatch,
/// The operands of a binary operator were incorrect.
Operator(BinOp),
/// A try expression returned a different error type to the previous try.
TryErrorMismatch,
/// The final value of a try expression was not a Result.
TryReturnResult,
}

View File

@ -0,0 +1,207 @@
use std::{collections::HashMap, sync::Arc};
use crate::{ast::Annotation, builtins::function};
use super::{environment::Environment, error::Error, Type, TypeConstructor};
/// The Hydrator takes an AST representing a type (i.e. a type annotation
/// for a function argument) and returns a Type for that annotation.
///
/// If a valid Type cannot be constructed it returns an error.
///
/// It keeps track of any type variables created. This is useful for:
///
/// - Determining if a generic type variable should be made into an
/// unbound type varable during type instantiation.
/// - Ensuring that the same type is constructed if the programmer
/// uses the same name for a type variable multiple times.
///
#[derive(Debug)]
pub struct Hydrator {
created_type_variables: HashMap<String, Arc<Type>>,
/// A rigid type is a generic type that was specified as being generic in
/// an annotation. As such it should never be instantiated into an unbound
/// variable. This type_id => name map is used for reporting the original
/// annotated name on error.
rigid_type_names: HashMap<u64, String>,
permit_new_type_variables: bool,
permit_holes: bool,
}
#[derive(Debug)]
pub struct ScopeResetData {
created_type_variables: HashMap<String, Arc<Type>>,
rigid_type_names: HashMap<u64, String>,
}
impl Hydrator {
pub fn new() -> Hydrator {
Hydrator {
created_type_variables: HashMap::new(),
rigid_type_names: HashMap::new(),
permit_new_type_variables: true,
permit_holes: false,
}
}
pub fn open_new_scope(&mut self) -> ScopeResetData {
let created_type_variables = self.created_type_variables.clone();
let rigid_type_names = self.rigid_type_names.clone();
ScopeResetData {
created_type_variables,
rigid_type_names,
}
}
pub fn close_scope(&mut self, data: ScopeResetData) {
self.created_type_variables = data.created_type_variables;
self.rigid_type_names = data.rigid_type_names;
}
pub fn disallow_new_type_variables(&mut self) {
self.permit_new_type_variables = false
}
pub fn permit_holes(&mut self, flag: bool) {
self.permit_holes = flag
}
/// A rigid type is a generic type that was specified as being generic in
/// an annotation. As such it should never be instantiated into an unbound
/// variable.
pub fn is_rigid(&self, id: &u64) -> bool {
self.rigid_type_names.contains_key(id)
}
pub fn rigid_names(&self) -> HashMap<u64, String> {
self.rigid_type_names.clone()
}
pub fn type_from_option_annotation<'a>(
&mut self,
ast: &Option<Annotation>,
environment: &mut Environment<'a>,
) -> Result<Arc<Type>, Error> {
match ast {
Some(ast) => self.type_from_annotation(ast, environment),
None => Ok(environment.new_unbound_var()),
}
}
/// Construct a Type from an AST Type annotation.
///
pub fn type_from_annotation<'a>(
&mut self,
annotation: &Annotation,
environment: &mut Environment<'a>,
) -> Result<Arc<Type>, Error> {
match annotation {
Annotation::Constructor {
location,
module,
name,
arguments: args,
} => {
// Hydrate the type argument AST into types
let mut argument_types = Vec::with_capacity(args.len());
for t in args {
let typ = self.type_from_annotation(t, environment)?;
argument_types.push((t.location(), typ));
}
// Look up the constructor
let TypeConstructor {
parameters,
tipo: return_type,
..
} = environment
.get_type_constructor(module, name, *location)?
.clone();
// Register the type constructor as being used if it is unqualifed.
// We do not track use of qualified type constructors as they may be
// used in another module.
if module.is_none() {
environment.increment_usage(name);
}
// Ensure that the correct number of arguments have been given to the constructor
if args.len() != parameters.len() {
return Err(Error::IncorrectTypeArity {
location: *location,
name: name.to_string(),
expected: parameters.len(),
given: args.len(),
});
}
// Instantiate the constructor type for this specific usage
let mut type_vars = HashMap::new();
#[allow(clippy::needless_collect)] // Not needless, used for size effects
let parameter_types: Vec<_> = parameters
.into_iter()
.map(|typ| environment.instantiate(typ, &mut type_vars, self))
.collect();
let return_type = environment.instantiate(return_type, &mut type_vars, self);
// Unify argument types with instantiated parameter types so that the correct types
// are inserted into the return type
for (parameter, (location, argument)) in
parameter_types.into_iter().zip(argument_types)
{
environment.unify(parameter, argument, location)?;
}
Ok(return_type)
}
Annotation::Fn { arguments, ret, .. } => {
let mut args = Vec::with_capacity(arguments.len());
for arg in arguments {
let arg = self.type_from_annotation(arg, environment)?;
args.push(arg);
}
let ret = self.type_from_annotation(ret, environment)?;
Ok(function(args, ret))
}
Annotation::Var { name, location, .. } => match self.created_type_variables.get(name) {
Some(var) => Ok(var.clone()),
None if self.permit_new_type_variables => {
let var = environment.new_generic_var();
self.rigid_type_names
.insert(environment.previous_uid(), name.clone());
self.created_type_variables
.insert(name.clone(), var.clone());
Ok(var)
}
None => Err(Error::UnknownType {
name: name.to_string(),
location: *location,
types: environment
.module_types
.keys()
.map(|t| t.to_string())
.collect(),
}),
},
Annotation::Hole { .. } if self.permit_holes => Ok(environment.new_unbound_var()),
Annotation::Hole { location, .. } => Err(Error::UnexpectedTypeHole {
location: *location,
}),
}
}
}

View File

@ -2,10 +2,12 @@ use std::collections::HashMap;
use crate::{ use crate::{
ast::{ModuleKind, TypedModule, UntypedModule}, ast::{ModuleKind, TypedModule, UntypedModule},
token::Token,
IdGenerator, IdGenerator,
}; };
use super::{ use super::{
environment::Environment,
error::{Error, Warning}, error::{Error, Warning},
Module, Module,
}; };
@ -18,5 +20,72 @@ pub fn module(
modules: &HashMap<String, Module>, modules: &HashMap<String, Module>,
warnings: &mut Vec<Warning>, warnings: &mut Vec<Warning>,
) -> Result<TypedModule, Error> { ) -> 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);
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());
// 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 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)?;
}
// 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!() todo!()
} }
fn validate_module_name(name: &str) -> Result<(), Error> {
if name == "aiken" {
return Err(Error::ReservedModuleName {
name: name.to_string(),
});
};
for segment in name.split('/') {
if str_to_keyword(segment).is_some() {
return Err(Error::KeywordInModuleName {
name: name.to_string(),
keyword: segment.to_string(),
});
}
}
Ok(())
}
pub fn str_to_keyword(word: &str) -> Option<Token> {
// Alphabetical keywords:
match word {
"as" => Some(Token::As),
"assert" => Some(Token::Assert),
"when" => Some(Token::When),
"const" => Some(Token::Const),
"fn" => Some(Token::Fn),
"if" => Some(Token::If),
"use" => Some(Token::Use),
"let" => Some(Token::Let),
"opaque" => Some(Token::Opaque),
"pub" => Some(Token::Pub),
"todo" => Some(Token::Todo),
"try" => Some(Token::Try),
"type" => Some(Token::Type),
_ => None,
}
}