From 4df3de0a0372bed060c7d1d5585a035b8875ac63 Mon Sep 17 00:00:00 2001 From: rvcas Date: Thu, 13 Oct 2022 18:34:25 -0400 Subject: [PATCH] feat: some boilerplate for typechecking --- crates/cli/src/error.rs | 35 ++++++++--- crates/cli/src/lib.rs | 3 + crates/cli/src/module.rs | 21 ++++++- crates/cli/src/project.rs | 105 +++++++++++++++++++++++++++++--- crates/lang/src/ast.rs | 1 + crates/lang/src/build.rs | 4 -- crates/lang/src/builtins.rs | 17 ++++++ crates/lang/src/expr.rs | 1 + crates/lang/src/lib.rs | 2 +- crates/lang/src/tipo.rs | 16 +++-- crates/lang/src/tipo/error.rs | 72 ++++++++++++++++++++++ crates/lang/src/tipo/infer.rs | 19 ++++++ examples/sample/scripts/swap.ak | 2 +- 13 files changed, 264 insertions(+), 34 deletions(-) delete mode 100644 crates/lang/src/build.rs create mode 100644 crates/lang/src/builtins.rs create mode 100644 crates/lang/src/tipo/error.rs create mode 100644 crates/lang/src/tipo/infer.rs diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index ce7ae86e..77585484 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -4,7 +4,7 @@ use std::{ path::PathBuf, }; -use aiken_lang::error::ParseError; +use aiken_lang::{error::ParseError, tipo}; use miette::{EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; #[allow(dead_code)] @@ -16,10 +16,17 @@ pub enum Error { first: PathBuf, second: PathBuf, }, + #[error("file operation failed")] FileIo { error: io::Error, path: PathBuf }, + #[error("cyclical module imports")] ImportCycle { modules: Vec }, + + /// Useful for returning many [`Error::Parse`] at once + #[error("a list of errors")] + List(Vec), + #[error("failed to parse")] Parse { path: PathBuf, @@ -29,9 +36,13 @@ pub enum Error { #[source] error: Box, }, - /// Useful for returning many [`Error::Parse`] at once - #[error("a list of errors")] - List(Vec), + + #[error("type checking failed")] + Type { + path: PathBuf, + src: String, + error: tipo::error::Error, + }, } impl Debug for Error { @@ -56,11 +67,12 @@ impl Debug for Error { impl miette::Diagnostic for Error { fn code<'a>(&'a self) -> Option> { match self { - Error::DuplicateModule { .. } => Some(Box::new("aiken::project::duplicate_module")), + Error::DuplicateModule { .. } => Some(Box::new("aiken::module::duplicate")), Error::FileIo { .. } => None, - Error::ImportCycle { .. } => Some(Box::new("aiken::project::cyclical_import")), - Error::Parse { .. } => Some(Box::new("aiken::parser")), + Error::ImportCycle { .. } => Some(Box::new("aiken::module::cyclical")), Error::List(_) => None, + Error::Parse { .. } => Some(Box::new("aiken::parser")), + Error::Type { .. } => Some(Box::new("aiken::typecheck")), } } @@ -76,8 +88,9 @@ impl miette::Diagnostic for Error { "try moving the shared code to a separate module that the others can depend on\n- {}", modules.join("\n- ") ))), - Error::Parse { error, .. } => error.kind.help(), Error::List(_) => None, + Error::Parse { error, .. } => error.kind.help(), + Error::Type { .. } => None, } } @@ -86,8 +99,9 @@ impl miette::Diagnostic for Error { Error::DuplicateModule { .. } => None, Error::FileIo { .. } => None, Error::ImportCycle { .. } => None, - Error::Parse { error, .. } => error.labels(), Error::List(_) => None, + Error::Parse { error, .. } => error.labels(), + Error::Type { error, .. } => error.labels(), } } @@ -96,8 +110,9 @@ impl miette::Diagnostic for Error { Error::DuplicateModule { .. } => None, Error::FileIo { .. } => None, Error::ImportCycle { .. } => None, - Error::Parse { src, .. } => Some(src), Error::List(_) => None, + Error::Parse { src, .. } => Some(src), + Error::Type { src, .. } => Some(src), } } } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 77ec66a1..b2a46373 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -2,3 +2,6 @@ pub mod config; pub mod error; pub mod module; pub mod project; + +pub use aiken_lang; +pub use uplc; diff --git a/crates/cli/src/module.rs b/crates/cli/src/module.rs index cd814f9d..204b7e7f 100644 --- a/crates/cli/src/module.rs +++ b/crates/cli/src/module.rs @@ -1,16 +1,15 @@ use std::{ collections::{HashMap, HashSet}, - ops::Deref, + ops::{Deref, DerefMut}, path::PathBuf, }; -use aiken_lang::ast::{ModuleKind, UntypedModule}; +use aiken_lang::ast::{ModuleKind, TypedModule, UntypedModule}; use petgraph::{algo, graph::NodeIndex, Direction, Graph}; use crate::error::Error; #[derive(Debug)] -#[allow(dead_code)] pub struct ParsedModule { pub path: PathBuf, pub name: String, @@ -120,6 +119,12 @@ impl Deref for ParsedModules { } } +impl DerefMut for ParsedModules { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + fn find_cycle( origin: NodeIndex, parent: NodeIndex, @@ -149,3 +154,13 @@ fn find_cycle( false } + +#[derive(Debug)] +pub struct CheckedModule { + pub name: String, + pub code: String, + pub input_path: PathBuf, + pub kind: ModuleKind, + pub ast: TypedModule, + // pub extra: ModuleExtra, +} diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index b1539406..c1682ac6 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -4,12 +4,12 @@ use std::{ path::{Path, PathBuf}, }; -use aiken_lang::ast::ModuleKind; +use aiken_lang::{ast::ModuleKind, builtins, tipo}; use crate::{ config::Config, error::Error, - module::{ParsedModule, ParsedModules}, + module::{CheckedModule, ParsedModule, ParsedModules}, }; #[derive(Debug)] @@ -20,20 +20,43 @@ pub struct Source { pub kind: ModuleKind, } +#[derive(Debug, PartialEq)] +pub enum Warning { + Type { + path: PathBuf, + src: String, + warning: tipo::error::Warning, + }, +} + +impl Warning { + pub fn from_type_warning(warning: tipo::error::Warning, path: PathBuf, src: String) -> Warning { + Warning::Type { path, warning, src } + } +} + pub struct Project { config: Config, + defined_modules: HashMap, + module_types: HashMap, root: PathBuf, sources: Vec, - defined_modules: HashMap, + warnings: Vec, } impl Project { pub fn new(config: Config, root: PathBuf) -> Project { + let mut module_types = HashMap::new(); + + module_types.insert("aiken".to_string(), builtins::prelude()); + Project { config, + defined_modules: HashMap::new(), + module_types, root, sources: vec![], - defined_modules: HashMap::new(), + warnings: vec![], } } @@ -44,6 +67,20 @@ impl Project { let processing_sequence = parsed_modules.sequence()?; + let checked_modules = self.type_check(parsed_modules, processing_sequence)?; + + println!("{:?}", checked_modules); + + Ok(()) + } + + fn read_source_files(&mut self) -> Result<(), Error> { + let lib = self.root.join("lib"); + let scripts = self.root.join("scripts"); + + self.aiken_files(&scripts, ModuleKind::Script)?; + self.aiken_files(&lib, ModuleKind::Lib)?; + Ok(()) } @@ -104,14 +141,62 @@ impl Project { } } - fn read_source_files(&mut self) -> Result<(), Error> { - let lib = self.root.join("lib"); - let scripts = self.root.join("scripts"); + fn type_check( + &mut self, + mut parsed_modules: ParsedModules, + processing_sequence: Vec, + ) -> Result, Error> { + let mut modules = Vec::with_capacity(parsed_modules.len() + 1); - self.aiken_files(&scripts, ModuleKind::Script)?; - self.aiken_files(&lib, ModuleKind::Lib)?; + for name in processing_sequence { + if let Some(ParsedModule { + name, + path, + code, + kind, + package, + ast, + }) = parsed_modules.remove(&name) + { + let mut type_warnings = Vec::new(); - Ok(()) + let ast = tipo::infer::module( + ast, + 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 + .into_iter() + .map(|w| Warning::from_type_warning(w, path.clone(), code.clone())); + + self.warnings.extend(type_warnings); + + // Register the types from this module so they can be imported into + // other modules. + self.module_types + .insert(name.clone(), ast.type_info.clone()); + + modules.push(CheckedModule { + kind, + // extra, + name, + code, + ast, + input_path: path, + }); + } + } + + Ok(modules) } fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> { diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 94118902..64d6cf50 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -481,6 +481,7 @@ pub struct IfBranch { pub location: Span, } +#[derive(Debug)] pub struct TypedRecordUpdateArg { pub label: String, pub location: Span, diff --git a/crates/lang/src/build.rs b/crates/lang/src/build.rs deleted file mode 100644 index 9070f46a..00000000 --- a/crates/lang/src/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub enum Origin { - Src, - Test, -} diff --git a/crates/lang/src/builtins.rs b/crates/lang/src/builtins.rs new file mode 100644 index 00000000..5e8e7b37 --- /dev/null +++ b/crates/lang/src/builtins.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +use crate::{ast::ModuleKind, tipo}; + +pub fn prelude() -> tipo::Module { + let mut prelude = tipo::Module { + name: vec!["gleam".to_string()], + package: "".to_string(), + kind: ModuleKind::Lib, + types: HashMap::new(), + types_constructors: HashMap::new(), + values: HashMap::new(), + accessors: HashMap::new(), + }; + + prelude +} diff --git a/crates/lang/src/expr.rs b/crates/lang/src/expr.rs index 9690c0e5..fd512e51 100644 --- a/crates/lang/src/expr.rs +++ b/crates/lang/src/expr.rs @@ -10,6 +10,7 @@ use crate::{ tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, }; +#[derive(Debug)] pub enum TypedExpr { Int { location: Span, diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 60e5aab0..eedb7b8f 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -1,5 +1,5 @@ pub mod ast; -pub mod build; +pub mod builtins; pub mod error; pub mod expr; pub mod lexer; diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index e0ffd953..a66b3688 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, collections::HashMap, sync::Arc}; -use crate::{ - ast::{Constant, FieldMap, Span, TypedConstant}, - build::Origin, -}; +use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant}; + +pub mod error; +pub mod infer; #[derive(Debug, Clone, PartialEq)] pub enum Type { @@ -105,9 +105,10 @@ pub enum ValueConstructorVariant { }, } +#[derive(Debug, Clone)] pub struct Module { pub name: Vec, - pub origin: Origin, + pub kind: ModuleKind, pub package: String, pub types: HashMap, pub types_constructors: HashMap>, @@ -115,6 +116,7 @@ pub struct Module { pub accessors: HashMap, } +#[derive(Debug, Clone)] pub struct TypeConstructor { pub public: bool, pub origin: Span, @@ -123,12 +125,14 @@ pub struct TypeConstructor { pub typ: Arc, } +#[derive(Debug, Clone)] pub struct AccessorsMap { pub public: bool, pub tipo: Arc, pub accessors: HashMap, } +#[derive(Debug, Clone)] pub struct RecordAccessor { // TODO: smaller int. Doesn't need to be this big pub index: u64, @@ -136,6 +140,7 @@ pub struct RecordAccessor { pub tipo: Arc, } +#[derive(Debug)] pub enum PatternConstructor { Record { name: String, @@ -143,6 +148,7 @@ pub enum PatternConstructor { }, } +#[derive(Debug)] pub enum ModuleValueConstructor { Record { name: String, diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs new file mode 100644 index 00000000..dc6add25 --- /dev/null +++ b/crates/lang/src/tipo/error.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use miette::Diagnostic; + +use crate::ast::{Span, TodoKind}; + +use super::Type; + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum Error {} + +#[derive(Debug, PartialEq, Clone)] +pub enum Warning { + Todo { + kind: TodoKind, + location: Span, + typ: Arc, + }, + + ImplicitlyDiscardedResult { + location: Span, + }, + + UnusedLiteral { + location: Span, + }, + + NoFieldsRecordUpdate { + location: Span, + }, + + AllFieldsRecordUpdate { + location: Span, + }, + + UnusedType { + location: Span, + imported: bool, + name: String, + }, + + UnusedConstructor { + location: Span, + imported: bool, + name: String, + }, + + UnusedImportedValue { + location: Span, + name: String, + }, + + UnusedImportedModule { + location: Span, + name: String, + }, + + UnusedPrivateModuleConstant { + location: Span, + name: String, + }, + + UnusedPrivateFunction { + location: Span, + name: String, + }, + + UnusedVariable { + location: Span, + name: String, + }, +} diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs new file mode 100644 index 00000000..a7cb368b --- /dev/null +++ b/crates/lang/src/tipo/infer.rs @@ -0,0 +1,19 @@ +use std::collections::HashMap; + +use crate::ast::{ModuleKind, TypedModule, UntypedModule}; + +use super::{ + error::{Error, Warning}, + Module, +}; + +pub fn module( + // ids: &UniqueIdGenerator, + mut module: UntypedModule, + kind: ModuleKind, + package: &str, + modules: &HashMap, + warnings: &mut Vec, +) -> Result { + todo!() +} diff --git a/examples/sample/scripts/swap.ak b/examples/sample/scripts/swap.ak index 73de6df7..b921bee5 100644 --- a/examples/sample/scripts/swap.ak +++ b/examples/sample/scripts/swap.ak @@ -5,7 +5,7 @@ pub type Datum { } pub type Redeemer { - Buy, + Buy Sell }