//! # The Conlang Type Checker
//!
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
#![feature(debug_closure_helpers)]
#![warn(clippy::all)]
/*
The type checker keeps track of a *global intern pool* for Types and Values
References to the intern pool are held by ID, and items cannot be freed from the pool EVER.
Items are inserted into their respective pools,
*/
pub mod key {
use cl_structures::intern_pool::*;
// define the index types
make_intern_key! {
/// Uniquely represents a [Def][1] in the [Def][1] [Pool]
///
/// [1]: crate::definition::Def
DefID,
}
}
pub mod definition {
use crate::{key::DefID, module::Module, type_kind::TypeKind, value_kind::ValueKind};
use cl_ast::{format::FmtPretty, Item, Meta, Visibility};
use std::fmt::Write;
#[derive(Clone, PartialEq, Eq)]
pub struct Def {
pub name: String,
pub vis: Visibility,
pub meta: Vec,
pub kind: DefKind,
pub source: Option- ,
pub module: Module,
}
impl Default for Def {
fn default() -> Self {
Self {
name: Default::default(),
vis: Default::default(),
meta: Default::default(),
kind: DefKind::Type(TypeKind::Module),
source: Default::default(),
module: Default::default(),
}
}
}
impl Def {
pub fn new_module(
name: String,
vis: Visibility,
meta: Vec,
parent: Option,
) -> Self {
Self {
name,
vis,
meta,
kind: DefKind::Type(TypeKind::Module),
source: None,
module: Module { parent, ..Default::default() },
}
}
}
impl std::fmt::Debug for Def {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { name, vis, meta, kind, source, module } = self;
f.debug_struct("Def")
.field("name", &name)
.field("vis", &vis)
.field_with("meta", |f| write!(f, "{meta:?}"))
.field("kind", &kind)
.field_with("source", |f| match source {
Some(item) => write!(f.pretty(), "{{\n{item}\n}}"),
None => todo!(),
})
.field("module", &module)
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DefKind {
/// A type, such as a ``
Type(TypeKind),
/// A value, such as a `const`, `static`, or `fn`
Value(ValueKind),
}
impl DefKind {
pub fn is_type(&self) -> bool {
matches!(self, Self::Type(_))
}
pub fn ty(&self) -> Option<&TypeKind> {
match self {
DefKind::Type(t) => Some(t),
_ => None,
}
}
pub fn is_value(&self) -> bool {
matches!(self, Self::Value(_))
}
pub fn value(&self) -> Option<&ValueKind> {
match self {
DefKind::Value(v) => Some(v),
_ => None,
}
}
}
}
pub mod type_kind {
//! A [TypeKind] represents an item in the Type Namespace
//! (a component of a [Project](crate::project::Project)).
use cl_ast::Visibility;
use std::{fmt::Debug, str::FromStr};
use crate::key::DefID;
/// The kind of a type
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypeKind {
/// A type which has not yet been resolved
Undecided,
/// An alias for an already-defined type
Alias(Option),
/// A primitive type, built-in to the compiler
Intrinsic(Intrinsic),
/// A user-defined abstract data type
Adt(Adt),
/// A reference to an already-defined type: &T
Ref(DefID),
/// A contiguous view of dynamically sized memory
Slice(DefID),
/// A function pointer which accepts multiple inputs and produces an output
FnPtr { args: Vec, rety: DefID },
/// The unit type
Empty,
/// The never type
Never,
/// The Self type
SelfTy,
/// An untyped module
Module,
}
/// A user-defined Abstract Data Type
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Adt {
/// A union-like enum type
Enum(Vec<(String, DefID)>),
CLikeEnum(Vec<(String, u128)>),
/// An enum with no fields, which can never be constructed
FieldlessEnum,
/// A structural product type with named members
Struct(Vec<(String, Visibility, DefID)>),
/// A structural product type with unnamed members
TupleStruct(Vec<(Visibility, DefID)>),
/// A structural product type of neither named nor unnamed members
UnitStruct,
/// A choose your own undefined behavior type
/// TODO: should unions be a language feature?
Union(Vec<(String, DefID)>),
}
/// The set of compiler-intrinsic types.
/// These primitive types have native implementations of the basic operations.
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Intrinsic {
/// An 8-bit signed integer: `#[intrinsic = "i8"]`
I8,
/// A 16-bit signed integer: `#[intrinsic = "i16"]`
I16,
/// A 32-bit signed integer: `#[intrinsic = "i32"]`
I32,
/// A 64-bit signed integer: `#[intrinsic = "i32"]`
I64,
// /// A 128-bit signed integer: `#[intrinsic = "i32"]`
// I128,
/// An 8-bit unsigned integer: `#[intrinsic = "u8"]`
U8,
/// A 16-bit unsigned integer: `#[intrinsic = "u16"]`
U16,
/// A 32-bit unsigned integer: `#[intrinsic = "u32"]`
U32,
/// A 64-bit unsigned integer: `#[intrinsic = "u64"]`
U64,
// /// A 128-bit unsigned integer: `#[intrinsic = "u128"]`
// U128,
/// A boolean (`true` or `false`): `#[intrinsic = "bool"]`
Bool,
}
impl FromStr for Intrinsic {
type Err = ();
fn from_str(s: &str) -> Result {
Ok(match s {
"i8" => Intrinsic::I8,
"i16" => Intrinsic::I16,
"i32" => Intrinsic::I32,
"i64" => Intrinsic::I64,
"u8" => Intrinsic::U8,
"u16" => Intrinsic::U16,
"u32" => Intrinsic::U32,
"u64" => Intrinsic::U64,
"bool" => Intrinsic::Bool,
_ => Err(())?,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Float {
F32 = 0x20,
F64,
}
}
pub mod value_kind {
//! A [ValueKind] represents an item in the Value Namespace
//! (a component of a [Project](crate::project::Project)).
use crate::typeref::TypeRef;
use cl_ast::Block;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ValueKind {
Undecided,
Const(TypeRef),
Static(TypeRef),
Fn {
// TODO: Store the variable bindings here!
args: Vec,
rety: TypeRef,
body: Block,
},
}
}
pub mod module {
//! A [Module] is a node in the Module Tree (a component of a
//! [Project](crate::project::Project))
use crate::key::DefID;
use std::collections::HashMap;
/// A [Module] is a node in the Module Tree (a component of a
/// [Project](crate::project::Project)).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Module {
pub parent: Option,
pub types: HashMap,
pub values: HashMap,
}
impl Module {
pub fn new(parent: DefID) -> Self {
Self { parent: Some(parent), ..Default::default() }
}
}
}
pub mod path {
use cl_ast::{Path as AstPath, PathPart};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Path<'p> {
pub absolute: bool,
pub parts: &'p [PathPart],
}
impl<'p> Path<'p> {
pub fn new(path: &'p AstPath) -> Self {
let AstPath { absolute, parts } = path;
Self { absolute: *absolute, parts }
}
pub fn relative(self) -> Self {
Self { absolute: false, ..self }
}
pub fn pop_front(self) -> Option {
let Self { absolute, parts } = self;
Some(Self { absolute, parts: parts.get(1..)? })
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn front(&self) -> Option<&PathPart> {
self.parts.first()
}
}
impl<'p> From<&'p AstPath> for Path<'p> {
fn from(value: &'p AstPath) -> Self {
Self::new(value)
}
}
impl std::fmt::Display for Path<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const SEPARATOR: &str = "::";
let Self { absolute, parts } = self;
if *absolute {
write!(f, "{SEPARATOR}")?
}
for (idx, part) in parts.iter().enumerate() {
write!(f, "{}{part}", if idx > 0 { SEPARATOR } else { "" })?;
}
Ok(())
}
}
}
pub mod project {
use crate::{
definition::{Def, DefKind},
key::DefID,
path::Path,
type_kind::TypeKind,
};
use cl_ast::{Identifier, PathPart, Visibility};
use cl_structures::intern_pool::Pool;
use std::ops::{Index, IndexMut};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Project {
pub pool: Pool,
pub module_root: DefID,
}
impl Project {
pub fn new() -> Self {
Self::default()
}
}
impl Default for Project {
fn default() -> Self {
let mut pool = Pool::default();
let module_root = pool.insert(Def::default());
// Insert the Never(!) type
let never = pool.insert(Def {
name: String::from("!"),
vis: Visibility::Public,
kind: DefKind::Type(TypeKind::Never),
..Default::default()
});
pool[module_root]
.module
.types
.insert(String::from("!"), never);
Self { pool, module_root }
}
}
impl Project {
pub fn parent_of(&self, module: DefID) -> Option {
self[module].module.parent
}
pub fn root_of(&self, module: DefID) -> DefID {
match self.parent_of(module) {
Some(module) => self.root_of(module),
None => module,
}
}
/// Resolves a path within a module tree, finding the innermost module.
/// Returns the remaining path parts.
pub fn get_type<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
// TODO: Cache module lookups
if path.absolute {
self.get_type(path.relative(), self.root_of(within))
} else if let Some(front) = path.front() {
let module = &self[within].module;
match front {
PathPart::SelfKw => self.get_type(path.pop_front()?, within),
PathPart::SuperKw => self.get_type(path.pop_front()?, module.parent?),
PathPart::Ident(Identifier(name)) => match module.types.get(name) {
Some(&submodule) => self.get_type(path.pop_front()?, submodule),
None => Some((within, path)),
},
}
} else {
Some((within, path))
}
}
pub fn get_value<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
match path.front()? {
PathPart::Ident(Identifier(name)) => Some((
self[within].module.values.get(name).copied()?,
path.pop_front()?,
)),
_ => None,
}
}
#[rustfmt::skip]
pub fn insert_type(&mut self, name: String, value: Def, parent: DefID) -> Option {
let id = self.pool.insert(value);
self[parent].module.types.insert(name, id)
}
#[rustfmt::skip]
pub fn insert_value(&mut self, name: String, value: Def, parent: DefID) -> Option {
let id = self.pool.insert(value);
self[parent].module.values.insert(name, id)
}
}
/// Implements [Index] and [IndexMut] for [Project]: `self.table[ID] -> Definition`
macro_rules! impl_index {
($(self.$table:ident[$idx:ty] -> $out:ty),*$(,)?) => {$(
impl Index<$idx> for Project {
type Output = $out;
fn index(&self, index: $idx) -> &Self::Output {
&self.$table[index]
}
}
impl IndexMut<$idx> for Project {
fn index_mut(&mut self, index: $idx) -> &mut Self::Output {
&mut self.$table[index]
}
}
)*};
}
impl_index! {
self.pool[DefID] -> Def,
// self.types[TypeID] -> TypeDef,
// self.values[ValueID] -> ValueDef,
}
}
pub mod name_collector {
//! Performs step 1 of type checking: Collecting all the names of things into [Module] units
use crate::{
definition::{Def, DefKind},
key,
project::Project,
type_kind::{Adt, TypeKind},
value_kind::ValueKind,
};
use cl_ast::*;
use std::ops::{Deref, DerefMut};
/// Collects types for future use
#[derive(Debug, PartialEq, Eq)]
pub struct NameCollector<'prj> {
/// A stack of the current modules
pub mod_stack: Vec,
/// The [Project], the type checker and resolver's central data store
pub project: &'prj mut Project,
}
impl<'prj> NameCollector<'prj> {
pub fn new(project: &'prj mut Project) -> Self {
// create a root module
Self { mod_stack: vec![project.module_root], project }
}
/// Gets the currently traversed parent module
pub fn parent(&self) -> Option {
self.mod_stack.last().copied()
}
}
impl Deref for NameCollector<'_> {
type Target = Project;
fn deref(&self) -> &Self::Target {
self.project
}
}
impl DerefMut for NameCollector<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.project
}
}
impl NameCollector<'_> {
pub fn file(&mut self, f: &File) -> Result<(), &'static str> {
let parent = self.parent().ok_or("No parent to add item to")?;
for item in &f.items {
let def = match &item.kind {
// modules
// types
ItemKind::Module(_) => {
self.module(item)?;
continue;
}
ItemKind::Enum(_) => Some(self.ty_enum(item)?),
ItemKind::Alias(_) => Some(self.ty_alias(item)?),
ItemKind::Struct(_) => Some(self.ty_struct(item)?),
// values processed by the value collector
ItemKind::Const(_) => Some(self.val_const(item)?),
ItemKind::Static(_) => Some(self.val_static(item)?),
ItemKind::Function(_) => Some(self.val_function(item)?),
ItemKind::Impl(_) => None,
};
let Some(def) = def else { continue };
match def.kind {
DefKind::Type(_) => {
if let Some(v) = self.insert_type(def.name.clone(), def, parent) {
panic!("Redefinition of type {} ({v:?})!", self[v].name)
}
}
DefKind::Value(_) => {
if let Some(v) = self.insert_value(def.name.clone(), def, parent) {
panic!("Redefinition of value {} ({v:?})!", self[v].name)
}
}
}
}
Ok(())
}
/// Collects a [Module]
pub fn module(&mut self, m: &Item) -> Result<(), &'static str> {
let Item { kind: ItemKind::Module(Module { name, kind }), vis, attrs, .. } = m else {
Err("module called on Item which was not an ItemKind::Module")?
};
let ModuleKind::Inline(kind) = kind else {
Err("Out-of-line modules not yet supported")?
};
let parent = self.parent().ok_or("No parent to add module to")?;
let module = self.pool.insert(Def::new_module(
name.0.clone(),
*vis,
attrs.meta.clone(),
Some(parent),
));
self[parent]
.module
.types
.insert(name.0.clone(), module)
.is_some()
.then(|| panic!("Error: redefinition of module {name}"));
self.mod_stack.push(module);
let out = self.file(kind);
self.mod_stack.pop();
out
}
}
/// Type collection
impl NameCollector<'_> {
/// Collects an [Item] of type [ItemKind::Enum]
pub fn ty_enum(&mut self, item: &Item) -> Result {
let Item { kind: ItemKind::Enum(Enum { name, kind }), vis, attrs, .. } = item else {
Err("Enum called on item which was not ItemKind::Enum")?
};
let kind = match kind {
EnumKind::NoVariants => DefKind::Type(TypeKind::Adt(Adt::FieldlessEnum)),
EnumKind::Variants(_) => DefKind::Type(TypeKind::Undecided),
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
/// Collects an [Item] of type [ItemKind::Alias]
pub fn ty_alias(&mut self, item: &Item) -> Result {
let Item { kind: ItemKind::Alias(Alias { to: name, from }), vis, attrs, .. } = item
else {
Err("Alias called on Item which was not ItemKind::Alias")?
};
let mut kind = match from {
Some(_) => DefKind::Type(TypeKind::Undecided),
None => DefKind::Type(TypeKind::Alias(None)),
};
for meta in &attrs.meta {
let Meta { name: meta_name, kind: meta_kind } = meta;
match (meta_name.0.as_str(), meta_kind) {
("intrinsic", MetaKind::Equals(Literal::String(intrinsic))) => {
kind = DefKind::Type(TypeKind::Intrinsic(
intrinsic.parse().map_err(|_| "unknown intrinsic type")?,
));
}
("intrinsic", MetaKind::Plain) => {
kind = DefKind::Type(TypeKind::Intrinsic(
name.0.parse().map_err(|_| "Unknown intrinsic type")?,
))
}
_ => {}
}
}
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
/// Collects an [Item] of type [ItemKind::Struct]
pub fn ty_struct(&mut self, item: &Item) -> Result {
let Item { kind: ItemKind::Struct(Struct { name, kind }), vis, attrs, .. } = item
else {
Err("Struct called on item which was not ItemKind::Struct")?
};
let kind = match kind {
StructKind::Empty => DefKind::Type(TypeKind::Adt(Adt::UnitStruct)),
StructKind::Tuple(_) => DefKind::Type(TypeKind::Undecided),
StructKind::Struct(_) => DefKind::Type(TypeKind::Undecided),
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind,
source: Some(item.clone()),
module: Default::default(),
})
}
}
/// Value collection
impl NameCollector<'_> {
pub fn val_const(&mut self, item: &Item) -> Result {
let Item { kind: ItemKind::Const(Const { name, .. }), vis, attrs, .. } = item else {
Err("Const called on Item which was not ItemKind::Const")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Value(ValueKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
pub fn val_static(&mut self, item: &Item) -> Result {
let Item { kind: ItemKind::Static(Static { name, .. }), vis, attrs, .. } = item else {
Err("Static called on Item which was not ItemKind::Static")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Type(TypeKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
pub fn val_function(&mut self, item: &Item) -> Result {
// TODO: treat function bodies like modules with internal items
let Item { kind: ItemKind::Function(Function { name, .. }), vis, attrs, .. } = item
else {
Err("val_function called on Item which was not ItemKind::Function")?
};
Ok(Def {
name: name.0.clone(),
vis: *vis,
meta: attrs.meta.clone(),
kind: DefKind::Value(ValueKind::Undecided),
source: Some(item.clone()),
module: Default::default(),
})
}
}
}
pub mod type_resolver {
//! Performs step 2 of type checking: Evaluating type definitions
#![allow(unused)]
use std::ops::{Deref, DerefMut};
use cl_ast::*;
use crate::{definition::Def, key::DefID, project::Project};
pub struct TypeResolver<'prj> {
pub project: &'prj mut Project,
}
impl Deref for TypeResolver<'_> {
type Target = Project;
fn deref(&self) -> &Self::Target {
self.project
}
}
impl DerefMut for TypeResolver<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.project
}
}
impl TypeResolver<'_> {
pub fn resolve(&mut self) -> Result {
#![allow(unused)]
for typedef in self.pool.iter_mut().filter(|v| v.kind.is_type()) {
let Def { name, vis, meta: attr, kind, source: Some(ref definition), module: _ } =
typedef
else {
continue;
};
match &definition.kind {
ItemKind::Alias(Alias { to: _, from: Some(from) }) => match &from.kind {
TyKind::Never => todo!(),
TyKind::Empty => todo!(),
TyKind::SelfTy => todo!(),
TyKind::Path(_) => todo!(),
TyKind::Tuple(_) => todo!(),
TyKind::Ref(_) => todo!(),
TyKind::Fn(_) => todo!(),
},
ItemKind::Alias(_) => {}
ItemKind::Const(_) => todo!(),
ItemKind::Static(_) => todo!(),
ItemKind::Module(_) => todo!(),
ItemKind::Function(_) => {}
ItemKind::Struct(_) => {}
ItemKind::Enum(_) => {}
ItemKind::Impl(_) => {}
}
}
Ok(true)
}
pub fn get_type(&self, kind: &TyKind) -> Option {
match kind {
TyKind::Never => todo!(),
TyKind::Empty => todo!(),
TyKind::SelfTy => todo!(),
TyKind::Path(_) => todo!(),
TyKind::Tuple(_) => todo!(),
TyKind::Ref(_) => todo!(),
TyKind::Fn(_) => todo!(),
}
None
}
}
}
pub mod typeref {
//! Stores type and reference info
use crate::key::DefID;
/// The Type struct represents all valid types, and can be trivially equality-compared
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypeRef {
/// You can only have a pointer chain 65535 pointers long.
ref_depth: u16,
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
kind: RefKind,
}
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RefKind {
/// A Concrete type has an associated [Def](super::definition::Def)
Concrete(DefID),
/// A Generic type is a *locally unique* comparable value,
/// valid only until the end of its typing context.
/// This is usually the surrounding function.
Generic(usize),
}
}
/*
/// What is an inference rule?
/// An inference rule is a specification with a set of predicates and a judgement
/// Let's give every type an ID
struct TypeID(usize);
/// Let's give every type some data:
struct TypeDef<'def> {
name: String,
definition: &'def Item,
}
and store them in a big vector of type descriptions:
struct TypeMap<'def> {
types: Vec>,
}
// todo: insertion of a type should yield a TypeID
// todo: impl index with TypeID
Let's store type information as either a concrete type or a generic type:
/// The Type struct represents all valid types, and can be trivially equality-compared
pub struct Type {
/// You can only have a pointer chain 65535 pointers long.
ref_depth: u16,
kind: TKind,
}
pub enum TKind {
Concrete(TypeID),
Generic(usize),
}
And assume I can specify a rule based on its inputs and outputs:
Rule {
operation: If,
/// The inputs field is populated by
inputs: [Concrete(BOOL), Generic(0), Generic(0)],
outputs: Generic(0),
/// This rule is compiler-intrinsic!
through: None,
}
Rule {
operation: Add,
inputs: [Concrete(I32), Concrete(I32)],
outputs: Concrete(I32),
/// This rule is not compiler-intrinsic (it is overloaded!)
through: Some(&ImplAddForI32::Add),
}
These rules can be stored in some kind of rule database:
let rules: Hashmap> {
}
*/
pub mod rule {
use crate::{key::DefID, typeref::TypeRef};
pub struct Rule {
/// What is this Rule for?
pub operation: (),
/// What inputs does it take?
pub inputs: Vec,
/// What output does it produce?
pub output: TypeRef,
/// Where did this rule come from?
pub through: Origin,
}
// TODO: Genericize
pub enum Operation {
Mul,
Div,
Rem,
Add,
Sub,
Deref,
Neg,
Not,
At,
Tilde,
Index,
If,
While,
For,
}
pub enum Origin {
/// This rule is built into the compiler
Intrinsic,
/// This rule is derived from an implementation on a type
Extrinsic(DefID),
}
}
pub mod typeck {
#![allow(unused)]
use cl_ast::*;
pub struct Context {
rules: (),
}
trait TypeCheck {}
}
//