From fe2b816f2734783ddf37b6bc822b62a822a5de59 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 25 Jul 2024 05:55:11 -0500 Subject: [PATCH] cl-typeck: Crate-spanning refactor part 2 - Removed all unreferenced files - Reimplemented missing/nonfunctional behavior - Added module documentation for most things - TODO: item-level docs on Entry(Mut) - Reparented the stages of Table population into the `stage` module. - TODO: rewrite type inference to use only the tools provided by Table. --- compiler/cl-typeck/examples/typeck.rs | 90 ++--- compiler/cl-typeck/src/categorize.rs | 69 ---- compiler/cl-typeck/src/entry.rs | 42 +- compiler/cl-typeck/src/entry/display.rs | 51 +-- compiler/cl-typeck/src/handle.rs | 4 +- compiler/cl-typeck/src/lib.rs | 163 ++------ compiler/cl-typeck/src/project.rs | 376 ------------------ compiler/cl-typeck/src/source.rs | 10 +- compiler/cl-typeck/src/stage/categorize.rs | 229 +++++++++++ compiler/cl-typeck/src/stage/implement.rs | 23 ++ compiler/cl-typeck/src/{ => stage}/import.rs | 86 +++- .../src/{inference.rs => stage/infer.rs} | 0 .../cl-typeck/src/{ => stage}/populate.rs | 4 +- compiler/cl-typeck/src/table.rs | 55 ++- compiler/cl-typeck/src/type_kind.rs | 15 +- compiler/cl-typeck/src/type_kind/display.rs | 10 +- compiler/cl-typeck/src/type_resolver.rs | 336 ---------------- compiler/cl-typeck/src/use_importer.rs | 125 ------ stdlib/test.cl | 17 +- 19 files changed, 524 insertions(+), 1181 deletions(-) delete mode 100644 compiler/cl-typeck/src/categorize.rs delete mode 100644 compiler/cl-typeck/src/project.rs create mode 100644 compiler/cl-typeck/src/stage/categorize.rs create mode 100644 compiler/cl-typeck/src/stage/implement.rs rename compiler/cl-typeck/src/{ => stage}/import.rs (55%) rename compiler/cl-typeck/src/{inference.rs => stage/infer.rs} (100%) rename compiler/cl-typeck/src/{ => stage}/populate.rs (97%) delete mode 100644 compiler/cl-typeck/src/type_resolver.rs delete mode 100644 compiler/cl-typeck/src/use_importer.rs diff --git a/compiler/cl-typeck/examples/typeck.rs b/compiler/cl-typeck/examples/typeck.rs index 556eaed..1de8838 100644 --- a/compiler/cl-typeck/examples/typeck.rs +++ b/compiler/cl-typeck/examples/typeck.rs @@ -1,7 +1,4 @@ -use cl_typeck::{ - entry::Entry, import::import, populate::Populator, table::Table, - type_expression::TypeExpression, -}; +use cl_typeck::{entry::Entry, stage::*, table::Table, type_expression::TypeExpression}; use cl_ast::{ ast_visitor::{Fold, Visit}, @@ -18,7 +15,7 @@ const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl"; const STDLIB: &str = include_str!("../../../stdlib/lib.cl"); // Colors -const C_MAIN: &str = ""; +const C_MAIN: &str = C_LISTING; const C_RESV: &str = "\x1b[35m"; const C_CODE: &str = "\x1b[36m"; const C_BYID: &str = "\x1b[95m"; @@ -148,56 +145,65 @@ fn get_by_id(prj: &mut Table) -> Result<(), RlError> { } println!(); - let Some(handle) = handle.nav(&path.parts) else { + let Some(entry) = handle.nav(&path.parts) else { Err("No results.")? }; - pretty_handle(handle)?; + pretty_handle(entry)?; Ok(Response::Accept) }) } -fn resolve_all(prj: &mut Table) -> Result<(), Box> { - println!("Resolving imports:"); - for (id, error) in import(prj) { - eprintln!("{error} in {} ({id})", id.to_entry(prj)) +fn resolve_all(table: &mut Table) -> Result<(), Box> { + for (id, error) in import(table) { + eprintln!("{error} in {} ({id})", id.to_entry(table)) } - // todo!("Resolve imports"); - // prj.resolve_imports()?; - // for id in prj.pool.keys() { - // resolve(prj, id)?; - // } - // println!("Types resolved successfully!"); + for handle in table.handle_iter() { + if let Err(error) = handle.to_entry_mut(table).categorize() { + eprintln!("{error}"); + } + } + + for handle in implement(table) { + eprintln!("Unable to reparent {} ({handle})", handle.to_entry(table)) + } + + println!("...Resolved!"); Ok(()) } -fn list_types(prj: &mut Table) { - for handle in prj.debug_handle_iter() { +fn list_types(table: &mut Table) { + for handle in table.debug_entry_iter() { let id = handle.id(); let kind = handle.kind().unwrap(); let name = handle.name().unwrap_or("".into()); - println!("{id:3}: {name:16}| {kind} {handle}"); + println!("{id:3}: {name:16}| {kind}: {handle}"); } } -fn pretty_handle(h: Entry) -> Result<(), std::io::Error> { +fn pretty_handle(entry: Entry) -> Result<(), std::io::Error> { use std::io::Write; let mut out = std::io::stdout().lock(); - let Some(kind) = h.kind() else { - return writeln!(out, "Invalid handle: {h}"); + let Some(kind) = entry.kind() else { + return writeln!(out, "{entry}"); }; write!(out, "{C_LISTING}{kind}")?; - if let Some(name) = h.name() { + + if let Some(name) = entry.name() { write!(out, " {name}")?; } - writeln!(out, "\x1b[0m: {h}")?; + writeln!(out, "\x1b[0m ({}): {entry}", entry.id())?; - if let Some(parent) = h.parent() { - writeln!(out, "- {C_LISTING}Parent\x1b[0m: {parent}")?; + if let Some(parent) = entry.parent() { + writeln!( + out, + "- {C_LISTING}Parent\x1b[0m: {parent} ({})", + parent.id() + )?; } - if let Some(span) = h.span() { + if let Some(span) = entry.span() { writeln!( out, "- {C_LISTING}Span:\x1b[0m ({}, {})", @@ -205,7 +211,7 @@ fn pretty_handle(h: Entry) -> Result<(), std::io::Error> { )?; } - match h.meta() { + match entry.meta() { Some(meta) if !meta.is_empty() => { writeln!(out, "- {C_LISTING}Meta:\x1b[0m")?; for meta in meta { @@ -215,46 +221,28 @@ fn pretty_handle(h: Entry) -> Result<(), std::io::Error> { _ => {} } - if let Some(children) = h.children() { + if let Some(children) = entry.children() { writeln!(out, "- {C_LISTING}Children:\x1b[0m")?; for (name, child) in children { writeln!( out, " - {C_LISTING}{name}\x1b[0m ({child}): {}", - h.with_id(*child) + entry.with_id(*child) )? } } - if let Some(imports) = h.imports() { + if let Some(imports) = entry.imports() { writeln!(out, "- {C_LISTING}Imports:\x1b[0m")?; for (name, child) in imports { writeln!( out, " - {C_LISTING}{name}\x1b[0m ({child}): {}", - h.with_id(*child) + entry.with_id(*child) )? } } - // let Some(vis) = handle.vis() else { - // return writeln!(stdout, "Invalid handle: {handle}"); - // }; - // writeln!(stdout, "{C_LISTING}{}\x1b[0m: {vis}{handle}", handle.id())?; - // if let Some(parent) = handle.parent() { - // } - // if let Some(types) = handle.types() { - // writeln!(stdout, "{C_LISTING}Types:\x1b[0m")?; - // for (name, def) in types { - // } - // } - // if let Some(values) = handle.values() { - // writeln!(stdout, "{C_LISTING}Values:\x1b[0m")?; - // for (name, def) in values { - // writeln!(stdout, "- {C_LISTING}{name}\x1b[0m: {}", handle.with(*def))? - // } - // } - // write!(stdout, "\x1b[0m") Ok(()) } diff --git a/compiler/cl-typeck/src/categorize.rs b/compiler/cl-typeck/src/categorize.rs deleted file mode 100644 index 3e8a90c..0000000 --- a/compiler/cl-typeck/src/categorize.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Categorizes all top-level entries in a table - -use crate::{ - handle::Handle, - source::Source, - table::Table, - type_expression::{Error as TypeEval, TypeExpression}, - type_kind::TypeKind, -}; -use cl_ast::*; - -/// Ensures a type entry exists for the provided handle in the table -pub fn categorize(table: &mut Table, node: Handle) -> CatResult<()> { - let Some(source) = table.source(node) else { - Err(Error::NoSource)? - }; - - match source { - Source::Root => {} - Source::Module(_) => {} - Source::Alias(a) => categorize_alias(table, node, a)?, - Source::Enum(_) => todo!(), - Source::Variant(_) => todo!(), - Source::Struct(_) => todo!(), - Source::Const(_) => todo!(), - Source::Static(_) => todo!(), - Source::Function(_) => todo!(), - Source::Local(_) => todo!(), - Source::Impl(_) => todo!(), - Source::Use(_) => todo!(), - Source::Ty(ty) => { - ty.evaluate(table, node)?; - } - } - - todo!("categorize {node} in {table:?}") -} - -pub fn categorize_alias(table: &mut Table, node: Handle, a: &Alias) -> CatResult<()> { - let kind = match &a.from { - Some(ty) => TypeKind::Alias(ty.evaluate(table, node)?), - None => TypeKind::Empty, - }; - table.set_ty(node, kind); - - Ok(()) -} - -pub fn categorize_const(table: &mut Table, node: Handle, c: &Const) -> CatResult<()> { - let kind = TypeKind::Alias(c.ty.evaluate(table, node)?); - - table.set_ty(node, kind); - Ok(()) -} - -type CatResult = Result; - -pub enum Error { - NoSource, - BadMeta(Meta), - Recursive(Handle), - TypeEval(TypeEval), -} - -impl From for Error { - fn from(value: TypeEval) -> Self { - Error::TypeEval(value) - } -} diff --git a/compiler/cl-typeck/src/entry.rs b/compiler/cl-typeck/src/entry.rs index f0b4437..0f2c010 100644 --- a/compiler/cl-typeck/src/entry.rs +++ b/compiler/cl-typeck/src/entry.rs @@ -1,10 +1,10 @@ -//! A [Handle] is an accessor for [Entries](Entry) in a [Table]. +//! An [Entry] is an accessor for [nodes](Handle) in a [Table]. //! -//! There are two kinds of handle: -//! - [Handle]: Provides getters for an entry's fields, and an implementation of +//! There are two kinds of entry: +//! - [Entry]: Provides getters for an entry's fields, and an implementation of //! [Display](std::fmt::Display) -//! - [HandleMut]: Provides setters for an entry's fields, and an [`as_ref`](HandleMut::as_ref) -//! method to demote to a [Handle]. +//! - [EntryMut]: Provides setters for an entry's fields, and an [`as_ref`](EntryMut::as_ref) method +//! to demote to an [Entry]. use std::collections::HashMap; @@ -14,11 +14,13 @@ use cl_structures::span::Span; use crate::{ handle::Handle, source::Source, + stage::categorize as cat, table::{NodeKind, Table}, + type_expression::{self as tex, TypeExpression}, type_kind::TypeKind, }; -pub mod display; +mod display; impl Handle { pub const fn to_entry<'t, 'a>(self, table: &'t Table<'a>) -> Entry<'t, 'a> { @@ -48,11 +50,11 @@ impl<'t, 'a> Entry<'t, 'a> { self.table } - pub const fn with_id(&self, id: Handle) -> Entry<'t, 'a> { + pub const fn with_id(&self, id: Handle) -> Entry<'_, 'a> { Self { table: self.table, id } } - pub fn nav(&self, path: &[PathPart]) -> Option> { + pub fn nav(&self, path: &[PathPart]) -> Option> { Some(Entry { id: self.table.nav(self.id, path)?, table: self.table }) } @@ -64,8 +66,8 @@ impl<'t, 'a> Entry<'t, 'a> { self.table.kind(self.id) } - pub fn parent(&self) -> Option<&Handle> { - self.table.parent(self.id) + pub fn parent(&self) -> Option> { + Some(Entry { id: *self.table.parent(self.id)?, ..*self }) } pub fn children(&self) -> Option<&HashMap> { @@ -92,12 +94,12 @@ impl<'t, 'a> Entry<'t, 'a> { self.table.source(self.id) } - pub fn impl_target(&self) -> Option { - self.table.impl_target(self.id) + pub fn impl_target(&self) -> Option> { + Some(Entry { id: self.table.impl_target(self.id)?, ..*self }) } - pub fn selfty(&self) -> Option { - self.table.selfty(self.id) + pub fn selfty(&self) -> Option> { + Some(Entry { id: self.table.selfty(self.id)?, ..*self }) } pub fn name(&self) -> Option { @@ -124,7 +126,17 @@ impl<'t, 'a> EntryMut<'t, 'a> { self.id } - /// Constructs a new Handle with the provided parent [DefID] + /// Evaluates a [TypeExpression] in this entry's context + pub fn evaluate(&mut self, ty: &impl TypeExpression) -> Result { + let Self { table, id } = self; + ty.evaluate(table, *id) + } + + pub fn categorize(&mut self) -> Result<(), cat::Error> { + cat::categorize(self.table, self.id) + } + + /// Constructs a new Handle with the provided parent [Handle] pub fn with_id(&mut self, parent: Handle) -> EntryMut<'_, 'a> { EntryMut { table: self.table, id: parent } } diff --git a/compiler/cl-typeck/src/entry/display.rs b/compiler/cl-typeck/src/entry/display.rs index 1eb35f5..fa7cbb1 100644 --- a/compiler/cl-typeck/src/entry/display.rs +++ b/compiler/cl-typeck/src/entry/display.rs @@ -12,29 +12,29 @@ fn write_name_or(h: Entry, f: &mut impl Write) -> fmt::Result { impl fmt::Display for Entry<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.kind().is_none() { + let Some(&kind) = self.kind() else { return write!(f, "", self.id); - } + }; if let Some(ty) = self.ty() { match ty { - TypeKind::Alias(id) => write!(f, "= {}", self.with_id(*id))?, - TypeKind::Intrinsic(kind) => write!(f, "{kind}")?, - TypeKind::Adt(adt) => write_adt(adt, self, f)?, + TypeKind::Instance(id) => write!(f, "{}", self.with_id(*id)), + TypeKind::Intrinsic(kind) => write!(f, "{kind}"), + TypeKind::Adt(adt) => write_adt(adt, self, f), &TypeKind::Ref(cnt, id) => { for _ in 0..cnt { f.write_str("&")?; } let h_id = self.with_id(id); - write_name_or(h_id, f)?; + write_name_or(h_id, f) } TypeKind::Slice(id) => { - write_name_or(self.with_id(*id), &mut f.delimit_with("[", "]"))?; + write_name_or(self.with_id(*id), &mut f.delimit_with("[", "]")) } &TypeKind::Array(t, cnt) => { let mut f = f.delimit_with("[", "]"); write_name_or(self.with_id(t), &mut f)?; - write!(f, "; {cnt}")?; + write!(f, "; {cnt}") } TypeKind::Tuple(ids) => { let mut f = f.delimit_with("(", ")"); @@ -44,18 +44,19 @@ impl fmt::Display for Entry<'_, '_> { } write_name_or(self.with_id(id), &mut f)?; } + Ok(()) } TypeKind::FnSig { args, rety } => { write!(f, "fn {} -> ", self.with_id(*args))?; - write_name_or(self.with_id(*rety), f)?; + write_name_or(self.with_id(*rety), f) } - TypeKind::Empty => write!(f, "()")?, - TypeKind::Never => write!(f, "!")?, - TypeKind::Module => write!(f, "module?")?, + TypeKind::Empty => write!(f, "()"), + TypeKind::Never => write!(f, "!"), + TypeKind::Module => write!(f, "module?"), } + } else { + write!(f, "{kind}") } - - Ok(()) } } @@ -63,35 +64,27 @@ fn write_adt(adt: &Adt, h: &Entry, f: &mut impl Write) -> fmt::Result { match adt { Adt::Enum(variants) => { let mut variants = variants.iter(); - separate(",", || { + separate(", ", || { variants.next().map(|(name, def)| { move |f: &mut Delimit<_>| match def { Some(def) => { - write!(f, "\n{name}: ")?; + write!(f, "{name}: ")?; write_name_or(h.with_id(*def), f) } - None => write!(f, "\n{name}"), + None => write!(f, "{name}"), } }) - })(f.delimit_with("enum {", "\n}")) + })(f.delimit_with("enum {", "}")) } - Adt::CLikeEnum(variants) => { - let mut variants = variants.iter(); - separate(",", || { - let (name, descrim) = variants.next()?; - Some(move |f: &mut Delimit<_>| write!(f, "\n{name} = {descrim}")) - })(f.delimit_with("enum {", "\n}")) - } - Adt::FieldlessEnum => write!(f, "enum"), Adt::Struct(members) => { let mut members = members.iter(); - separate(",", || { + separate(", ", || { let (name, vis, id) = members.next()?; Some(move |f: &mut Delimit<_>| { - write!(f, "\n{vis}{name}: ")?; + write!(f, "{vis}{name}: ")?; write_name_or(h.with_id(*id), f) }) - })(f.delimit_with("struct {", "\n}")) + })(f.delimit_with("struct {", "}")) } Adt::TupleStruct(members) => { let mut members = members.iter(); diff --git a/compiler/cl-typeck/src/handle.rs b/compiler/cl-typeck/src/handle.rs index 3d530ee..128bad4 100644 --- a/compiler/cl-typeck/src/handle.rs +++ b/compiler/cl-typeck/src/handle.rs @@ -1,8 +1,10 @@ +//! A [Handle] uniquely represents an entry in the [Table](crate::table::Table) + use cl_structures::index_map::*; // define the index types make_index! { - /// Uniquely represents an entry in the [item context](crate::sym_table) + /// Uniquely represents an entry in the [Table](crate::table::Table) Handle, } diff --git a/compiler/cl-typeck/src/lib.rs b/compiler/cl-typeck/src/lib.rs index 5b3b6da..d0b9fc5 100644 --- a/compiler/cl-typeck/src/lib.rs +++ b/compiler/cl-typeck/src/lib.rs @@ -4,71 +4,28 @@ //! //! This crate is a major work-in-progress. //! -//! # The [Project](project::Project)™ -//! Contains [item definitions](definition) and type expression information. +//! # The [Table](table::Table)™ +//! A directed graph of nodes and their dependencies. //! -//! *Every* definition is itself a module, and can contain arbitrarily nested items -//! as part of the [Module](module::Module) tree. +//! Contains [item definitions](handle) and [type expression](type_expression) information. //! -//! The Project keeps track of a *global intern pool* of definitions, which are -//! trivially comparable by [DefID](key::DefID). Note that, for item definitions, -//! identical types in different modules DO NOT COMPARE EQUAL under this constraint. -//! However, so-called "anonymous" types *do not* follow this rule, as their -//! definitions are constructed dynamically and ensured to be unique. -// Note: it's a class invariant that named types are not added -// to the anon-types list. Please keep it that way. ♥ Thanks! +//! *Every* item is itself a module, and can contain arbitrarily nested items +//! as part of the item graph +//! +//! The table, additionally, has some queues for use in external algorithms, +//! detailed in the [stage] module. //! //! # Namespaces -//! Within a Project, [definitions](definition::Def) are classified into two namespaces: -//! -//! ## Type Namespace: -//! - Modules -//! - Structs -//! - Enums -//! - Type aliases -//! -//! ## Value Namespace: -//! - Functions -//! - Constants -//! - Static variables -//! -//! There is a *key* distinction between the two namespaces when it comes to -//! [Path](path::Path) traversal: Only items in the Type Namespace will be considered -//! as an intermediate path target. This means items in the Value namespace are -//! entirely *opaque*, and form a one-way barrier. Items outside a Value can be -//! referred to within the Value, but items inside a Value *cannot* be referred to -//! outside the Value. +//! Each item in the graph is given its own namespace, which is further separated into +//! two distinct parts: +//! - Children of an item are direct descendents (i.e. their `parent` is a handle to the item) +//! - Imports of an item are indirect descendents created by `use` or `impl` directives. They are +//! shadowed by Children with the same name. //! //! # Order of operations: -//! Currently, the process of type resolution goes as follows: -//! -//! 1. Traverse the AST, [collecting all Items into item definitions](name_collector) -//! 2. Eagerly [resolve `use` imports](use_importer) -//! 3. Traverse all [definitions](definition::Def), and [resolve the types for every -//! item](type_resolver) -//! 4. TODO: Construct a typed AST for expressions, and type-check them +//! For order-of-operations information, see the [stage] module. #![warn(clippy::all)] -/* -How do I flesh out modules in an incremental way? - -1. Visit all *modules* and create nodes in a module tree for them -- This can be done by holding mutable references to submodules! - - -Module: - values: Map(name -> Value), - types: Map(name -> Type), - -Value: Either - function: { signature: Type, body: FunctionBody } - static: { Mutability, ty: Type, init: ConstEvaluationResult } - const: { ty: Type, init: ConstEvaluationResult } - -*/ - -pub mod inference; - pub(crate) mod format_utils; pub mod table; @@ -83,75 +40,35 @@ pub mod type_kind; pub mod type_expression; -pub mod populate; +pub mod stage { + //! Type collection, evaluation, checking, and inference passes. + //! + //! # Order of operations + //! 1. [mod@populate]: Populate the graph with nodes for every named item. + //! 2. [mod@import]: Import the `use` nodes discovered in [Stage 1](populate). + //! 3. [mod@categorize]: Categorize the nodes according to textual type information. + //! - Creates anonymous types (`fn(T) -> U`, `&T`, `[T]`, etc.) as necessary to fill in the + //! type graph + //! - Creates a new struct type for every enum struct-variant. + //! 4. [mod@implement]: Import members of implementation modules into types. -pub mod import; + pub use populate::Populator; + /// Stage 1: Populate the graph with nodes. + pub mod populate; -pub mod categorize; + pub use import::import; + /// Stage 2: Import the `use` nodes discovered in Stage 1. + pub mod import; -/* + pub use categorize::categorize; + /// Stage 3: Categorize the nodes according to textual type information. + pub mod categorize; -LET THERE BE NOTES: + pub use implement::implement; + /// Stage 4: Import members of `impl` blocks into their corresponding types. + pub mod implement; -/// 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, + // TODO: Make type inference stage 5 + // TODO: Use the type information stored in the [table] + pub mod infer; } - -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> { - -} - -*/ diff --git a/compiler/cl-typeck/src/project.rs b/compiler/cl-typeck/src/project.rs deleted file mode 100644 index 1474ad4..0000000 --- a/compiler/cl-typeck/src/project.rs +++ /dev/null @@ -1,376 +0,0 @@ -//! A [Project] contains a tree of [Def]initions, referred to by their [Path] -use crate::{ - definition::{Def, DefKind, TypeKind}, - handle::DefID, - module, - node::{Node, NodeSource}, - path::Path, -}; -use cl_ast::PathPart; -use cl_structures::index_map::IndexMap; -use std::{ - collections::HashMap, - ops::{Index, IndexMut}, -}; - -use self::evaluate::EvaluableTypeExpression; - -#[derive(Clone, Debug)] -pub struct Project<'a> { - pub pool: IndexMap>, - /// Stores anonymous tuples, function pointer types, etc. - pub anon_types: HashMap, - pub root: DefID, -} - -impl Project<'_> { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for Project<'_> { - fn default() -> Self { - const ROOT_PATH: cl_ast::Path = cl_ast::Path { absolute: true, parts: Vec::new() }; - - let mut pool = IndexMap::default(); - let root = pool.insert(Def { - module: Default::default(), - kind: DefKind::Type(TypeKind::Module), - node: Node::new(ROOT_PATH, Some(NodeSource::Root)), - }); - let never = pool.insert(Def { - module: module::Module::new(root), - kind: DefKind::Type(TypeKind::Never), - node: Node::new(ROOT_PATH, None), - }); - let empty = pool.insert(Def { - module: module::Module::new(root), - kind: DefKind::Type(TypeKind::Empty), - node: Node::new(ROOT_PATH, None), - }); - - let mut anon_types = HashMap::new(); - anon_types.insert(TypeKind::Empty, empty); - anon_types.insert(TypeKind::Never, never); - - Self { pool, root, anon_types } - } -} - -impl<'a> Project<'a> { - 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, - } - } - - /// Returns the DefID of the Self type within the given DefID's context - pub fn selfty_of(&self, node: DefID) -> Option { - match self[node].kind { - DefKind::Impl(id) => Some(id), - DefKind::Type(_) => Some(node), - _ => self.selfty_of(self.parent_of(node)?), - } - } - - pub fn get<'p>( - &self, - path: Path<'p>, - within: DefID, - ) -> Option<(Option, Option, Path<'p>)> { - if path.absolute { - return self.get(path.relative(), self.root_of(within)); - } - match path.as_ref() { - [PathPart::SuperKw, ..] => self.get(path.pop_front()?, self.parent_of(within)?), - [PathPart::SelfTy, ..] => self.get(path.pop_front()?, self.selfty_of(within)?), - [PathPart::SelfKw, ..] => self.get(path.pop_front()?, within), - [PathPart::Ident(name)] => { - let (ty, val) = self[within].module.get(*name); - - // Transparent nodes can be looked through in reverse - if self[within].is_transparent() { - let lookback = self.parent_of(within).and_then(|p| self.get(path, p)); - if let Some((subty, subval, path)) = lookback { - return Some((ty.or(subty), val.or(subval), path)); - } - } - Some((ty, val, path.pop_front()?)) - } - [PathPart::Ident(name), ..] => { - // TODO: This is currently too permissive, and treats undecided nodes as if they're - // always transparent, among other issues. - let (tysub, _, _) = match self[within].is_transparent() { - true => self.get(path.front()?, within)?, - false => (None, None, path), - }; - let ty = self[within].module.get_type(*name).or(tysub)?; - self.get(path.pop_front()?, ty) - } - [] => Some((Some(within), None, path)), - } - } - - /// Resolves a path within a module tree, finding the innermost module. - /// Returns the remaining path parts. - pub fn get_type<'p>(&self, path: Path<'p>, within: DefID) -> Option<(DefID, Path<'p>)> { - if path.absolute { - self.get_type(path.relative(), self.root_of(within)) - } else if let Some(front) = path.first() { - 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::SelfTy => self.get_type(path.pop_front()?, self.selfty_of(within)?), - PathPart::Ident(name) => match module.types.get(name) { - Some(&submodule) => self.get_type(path.pop_front()?, submodule), - None => Some((within, path)), - }, - } - } else { - Some((within, path)) - } - } - - /// Inserts the type returned by the provided closure iff the TypeKind doesn't already exist - /// - /// Assumes `kind` uniquely identifies the type! - pub fn insert_anonymous_type( - &mut self, - kind: TypeKind, - def: impl FnOnce() -> Def<'a>, - ) -> DefID { - *(self - .anon_types - .entry(kind) - .or_insert_with(|| self.pool.insert(def()))) - } - - pub fn evaluate(&mut self, expr: &T, parent: DefID) -> Result - where T: EvaluableTypeExpression { - expr.evaluate(self, parent) - } -} - -impl<'a> Index for Project<'a> { - type Output = Def<'a>; - fn index(&self, index: DefID) -> &Self::Output { - &self.pool[index] - } -} -impl IndexMut for Project<'_> { - fn index_mut(&mut self, index: DefID) -> &mut Self::Output { - &mut self.pool[index] - } -} - -pub mod evaluate { - //! An [EvaluableTypeExpression] is a component of a type expression tree - //! or an intermediate result of expression evaluation. - - use super::*; - use crate::module; - use cl_ast::{Sym, Ty, TyArray, TyFn, TyKind, TyRef, TySlice, TyTuple}; - - /// Things that can be evaluated as a type expression - pub trait EvaluableTypeExpression { - /// The result of type expression evaluation - type Out; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result; - } - - impl EvaluableTypeExpression for Ty { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, id: DefID) -> Result { - self.kind.evaluate(prj, id) - } - } - - impl EvaluableTypeExpression for TyKind { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let id = match self { - // TODO: reduce duplication here - TyKind::Never => prj.anon_types[&TypeKind::Never], - TyKind::Empty => prj.anon_types[&TypeKind::Empty], - // TyKind::Path must be looked up explicitly - TyKind::Path(path) => path.evaluate(prj, parent)?, - TyKind::Slice(slice) => slice.evaluate(prj, parent)?, - TyKind::Array(array) => array.evaluate(prj, parent)?, - TyKind::Tuple(tup) => tup.evaluate(prj, parent)?, - TyKind::Ref(tyref) => tyref.evaluate(prj, parent)?, - TyKind::Fn(tyfn) => tyfn.evaluate(prj, parent)?, - }; - // println!("{self} => {id:?}"); - - Ok(id) - } - } - - impl EvaluableTypeExpression for Sym { - type Out = DefID; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - prj[parent] - .module - .types - .get(self) - .copied() - .ok_or_else(|| format!("{self} is not a member of {:?}", prj[parent].name())) - } - } - - impl EvaluableTypeExpression for TySlice { - type Out = DefID; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let ty = self.ty.evaluate(prj, parent)?; - let root = prj.root; - let id = prj.insert_anonymous_type(TypeKind::Slice(ty), move || Def { - module: module::Module::new(root), - node: Node::new(Default::default(), None), - kind: DefKind::Type(TypeKind::Slice(ty)), - }); - - Ok(id) - } - } - - impl EvaluableTypeExpression for TyArray { - type Out = DefID; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let kind = TypeKind::Array(self.ty.evaluate(prj, parent)?, self.count); - let root = prj.root; - let id = prj.insert_anonymous_type(kind.clone(), move || Def { - module: module::Module::new(root), - node: Node::new(Default::default(), None), - kind: DefKind::Type(kind), - }); - - Ok(id) - } - } - - impl EvaluableTypeExpression for TyTuple { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let types = self.types.evaluate(prj, parent)?; - let root = prj.root; - let id = prj.insert_anonymous_type(TypeKind::Tuple(types.clone()), move || Def { - module: module::Module::new(root), - node: Node::new(Default::default(), None), - kind: DefKind::Type(TypeKind::Tuple(types)), - }); - - Ok(id) - } - } - impl EvaluableTypeExpression for TyRef { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let TyRef { count, mutable: _, to } = self; - let to = to.evaluate(prj, parent)?; - - let root = prj.root; - let id = prj.insert_anonymous_type(TypeKind::Ref(*count, to), move || Def { - module: module::Module::new(root), - node: Node::new(Default::default(), None), - kind: DefKind::Type(TypeKind::Ref(*count, to)), - }); - Ok(id) - } - } - impl EvaluableTypeExpression for TyFn { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let TyFn { args, rety } = self; - - let args = args.evaluate(prj, parent)?; - let rety = match rety { - Some(rety) => rety.evaluate(prj, parent)?, - _ => TyKind::Empty.evaluate(prj, parent)?, - }; - - let root = prj.root; - let id = prj.insert_anonymous_type(TypeKind::FnSig { args, rety }, || Def { - module: module::Module::new(root), - node: Node::new(Default::default(), None), - kind: DefKind::Type(TypeKind::FnSig { args, rety }), - }); - Ok(id) - } - } - - impl EvaluableTypeExpression for cl_ast::Path { - type Out = DefID; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - Path::from(self).evaluate(prj, parent) - } - } - impl EvaluableTypeExpression for PathPart { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - match self { - PathPart::SuperKw => prj - .parent_of(parent) - .ok_or_else(|| "Attempt to get super of root".into()), - PathPart::SelfKw => Ok(parent), - PathPart::SelfTy => prj - .selfty_of(parent) - .ok_or_else(|| "Attempt to get Self outside a Self-able context".into()), - PathPart::Ident(name) => name.evaluate(prj, parent), - } - } - } - impl<'a> EvaluableTypeExpression for Path<'a> { - type Out = DefID; - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let (tid, vid, path) = prj.get(*self, parent).ok_or("Failed to traverse path")?; - if !path.is_empty() { - Err(format!("Could not traverse past boundary: {path}"))?; - } - match (tid, vid) { - (Some(ty), _) => Ok(ty), - (None, Some(val)) => Ok(val), - (None, None) => Err(format!("No type or value found at path {self}")), - } - } - } - impl EvaluableTypeExpression for [T] { - type Out = Vec; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - let mut types = vec![]; - for value in self { - types.push(value.evaluate(prj, parent)?) - } - - Ok(types) - } - } - impl EvaluableTypeExpression for Option { - type Out = Option; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - Ok(match self { - Some(v) => Some(v.evaluate(prj, parent)?), - None => None, - }) - } - } - impl EvaluableTypeExpression for Box { - type Out = T::Out; - - fn evaluate(&self, prj: &mut Project, parent: DefID) -> Result { - self.as_ref().evaluate(prj, parent) - } - } -} diff --git a/compiler/cl-typeck/src/source.rs b/compiler/cl-typeck/src/source.rs index 5b8f587..90fdca7 100644 --- a/compiler/cl-typeck/src/source.rs +++ b/compiler/cl-typeck/src/source.rs @@ -37,12 +37,12 @@ impl<'a> Source<'a> { } } - /// Returns `true` if this [NodeSource] defines a named value + /// Returns `true` if this [Source] defines a named value pub fn is_named_value(&self) -> bool { matches!(self, Self::Const(_) | Self::Static(_) | Self::Function(_)) } - /// Returns `true` if this [NodeSource] defines a named type + /// Returns `true` if this [Source] defines a named type pub fn is_named_type(&self) -> bool { matches!( self, @@ -50,17 +50,17 @@ impl<'a> Source<'a> { ) } - /// Returns `true` if this [NodeSource] refers to a [Ty] with no name + /// Returns `true` if this [Source] refers to a [Ty] with no name pub fn is_anon_type(&self) -> bool { matches!(self, Self::Ty(_)) } - /// Returns `true` if this [NodeSource] refers to an [Impl] block + /// Returns `true` if this [Source] refers to an [Impl] block pub fn is_impl(&self) -> bool { matches!(self, Self::Impl(_)) } - /// Returns `true` if this [NodeSource] refers to a [Use] import + /// Returns `true` if this [Source] refers to a [Use] import pub fn is_use_import(&self) -> bool { matches!(self, Self::Use(_)) } diff --git a/compiler/cl-typeck/src/stage/categorize.rs b/compiler/cl-typeck/src/stage/categorize.rs new file mode 100644 index 0000000..4a6c69e --- /dev/null +++ b/compiler/cl-typeck/src/stage/categorize.rs @@ -0,0 +1,229 @@ +//! Categorizes an entry in a table according to its embedded type information + +use crate::{ + handle::Handle, + source::Source, + table::{NodeKind, Table}, + type_expression::{Error as TypeEval, TypeExpression}, + type_kind::{Adt, TypeKind}, +}; +use cl_ast::*; + +/// Ensures a type entry exists for the provided handle in the table +pub fn categorize(table: &mut Table, node: Handle) -> CatResult<()> { + if let Some(meta) = table.meta(node) { + for meta @ Meta { name, kind } in meta { + if let ("intrinsic", MetaKind::Equals(Literal::String(s))) = (&**name, kind) { + let kind = + TypeKind::Intrinsic(s.parse().map_err(|_| Error::BadMeta(meta.clone()))?); + table.set_ty(node, kind); + return Ok(()); + } + } + } + + let Some(source) = table.source(node) else { + return Ok(()); + }; + + match source { + Source::Root => Ok(()), + Source::Module(_) => Ok(()), + Source::Alias(a) => cat_alias(table, node, a), + Source::Enum(e) => cat_enum(table, node, e), + Source::Variant(_) => Ok(()), + Source::Struct(s) => cat_struct(table, node, s), + Source::Const(c) => cat_const(table, node, c), + Source::Static(s) => cat_static(table, node, s), + Source::Function(f) => cat_function(table, node, f), + Source::Local(l) => cat_local(table, node, l), + Source::Impl(i) => cat_impl(table, node, i), + Source::Use(_) => Ok(()), + Source::Ty(ty) => ty + .evaluate(table, node) + .map_err(|e| Error::TypeEval(e, " while categorizing a type")) + .map(drop), + } +} + +fn parent(table: &Table, node: Handle) -> Handle { + table.parent(node).copied().unwrap_or(node) +} + +fn cat_alias(table: &mut Table, node: Handle, a: &Alias) -> CatResult<()> { + let parent = parent(table, node); + let kind = match &a.from { + Some(ty) => TypeKind::Instance( + ty.evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing an alias"))?, + ), + None => TypeKind::Empty, + }; + table.set_ty(node, kind); + + Ok(()) +} + +fn cat_struct(table: &mut Table, node: Handle, s: &Struct) -> CatResult<()> { + let parent = parent(table, node); + let Struct { name: _, kind } = s; + let kind = match kind { + StructKind::Empty => TypeKind::Adt(Adt::UnitStruct), + StructKind::Tuple(types) => { + let mut out = vec![]; + for ty in types { + out.push((Visibility::Public, ty.evaluate(table, parent)?)) + } + TypeKind::Adt(Adt::TupleStruct(out)) + } + StructKind::Struct(members) => { + let mut out = vec![]; + for m in members { + out.push(cat_member(table, node, m)?) + } + TypeKind::Adt(Adt::Struct(out)) + } + }; + + table.set_ty(node, kind); + Ok(()) +} + +fn cat_member( + table: &mut Table, + node: Handle, + m: &StructMember, +) -> CatResult<(Sym, Visibility, Handle)> { + let StructMember { vis, name, ty } = m; + Ok((*name, *vis, ty.evaluate(table, node)?)) +} + +fn cat_enum<'a>(table: &mut Table<'a>, node: Handle, e: &'a Enum) -> CatResult<()> { + let Enum { name: _, kind } = e; + let kind = match kind { + EnumKind::NoVariants => TypeKind::Adt(Adt::Enum(vec![])), + EnumKind::Variants(variants) => { + let mut out_vars = vec![]; + for v in variants { + out_vars.push(cat_variant(table, node, v)?) + } + TypeKind::Adt(Adt::Enum(out_vars)) + } + }; + + table.set_ty(node, kind); + Ok(()) +} + +fn cat_variant<'a>( + table: &mut Table<'a>, + node: Handle, + v: &'a Variant, +) -> CatResult<(Sym, Option)> { + let parent = parent(table, node); + let Variant { name, kind } = v; + match kind { + VariantKind::Plain => Ok((*name, None)), + VariantKind::CLike(c) => todo!("enum-variant constant {c}"), + VariantKind::Tuple(ty) => { + let ty = ty + .evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing a variant"))?; + Ok((*name, Some(ty))) + } + VariantKind::Struct(members) => { + let mut out = vec![]; + for m in members { + out.push(cat_member(table, node, m)?) + } + let kind = TypeKind::Adt(Adt::Struct(out)); + + let mut h = node.to_entry_mut(table); + let mut variant = h.new_entry(NodeKind::Type); + variant.set_source(Source::Variant(v)); + variant.set_ty(kind); + Ok((*name, Some(variant.id()))) + } + } +} + +fn cat_const(table: &mut Table, node: Handle, c: &Const) -> CatResult<()> { + let parent = parent(table, node); + let kind = TypeKind::Instance( + c.ty.evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing a const"))?, + ); + table.set_ty(node, kind); + Ok(()) +} + +fn cat_static(table: &mut Table, node: Handle, s: &Static) -> CatResult<()> { + let parent = parent(table, node); + let kind = TypeKind::Instance( + s.ty.evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing a static"))?, + ); + table.set_ty(node, kind); + Ok(()) +} + +fn cat_function(table: &mut Table, node: Handle, f: &Function) -> CatResult<()> { + let parent = parent(table, node); + let kind = TypeKind::Instance( + f.sign + .evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing a function"))?, + ); + table.set_ty(node, kind); + Ok(()) +} + +fn cat_local(table: &mut Table, node: Handle, l: &Let) -> CatResult<()> { + let parent = parent(table, node); + if let Some(ty) = &l.ty { + let kind = ty + .evaluate(table, parent) + .map_err(|e| Error::TypeEval(e, " while categorizing a let binding"))?; + table.set_ty(node, TypeKind::Instance(kind)); + } + Ok(()) +} + +fn cat_impl(table: &mut Table, node: Handle, i: &Impl) -> CatResult<()> { + let parent = parent(table, node); + let Impl { target, body: _ } = i; + let target = match target { + ImplKind::Type(t) => t.evaluate(table, parent), + ImplKind::Trait { impl_trait: _, for_type: t } => t.evaluate(table, parent), + }?; + + table.set_impl_target(node, target); + Ok(()) +} + +type CatResult = Result; + +#[derive(Clone, Debug)] +pub enum Error { + BadMeta(Meta), + Recursive(Handle), + TypeEval(TypeEval, &'static str), +} + +impl From for Error { + fn from(value: TypeEval) -> Self { + Error::TypeEval(value, "") + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::BadMeta(meta) => write!(f, "Unknown meta attribute: #[{meta}]"), + Error::Recursive(id) => { + write!(f, "Encountered recursive type without indirection: {id}") + } + Error::TypeEval(e, during) => write!(f, "{e}{during}"), + } + } +} diff --git a/compiler/cl-typeck/src/stage/implement.rs b/compiler/cl-typeck/src/stage/implement.rs new file mode 100644 index 0000000..775178e --- /dev/null +++ b/compiler/cl-typeck/src/stage/implement.rs @@ -0,0 +1,23 @@ +use crate::{handle::Handle, table::Table}; + + pub fn implement(table: &mut Table) -> Vec { + let pending = std::mem::take(&mut table.impls); + let mut errors = vec![]; + for node in pending { + if let Err(e) = impl_one(table, node) { + errors.push(e); + } + } + errors + } + + pub fn impl_one(table: &mut Table, node: Handle) -> Result<(), Handle> { + let Some(target) = table.impl_target(node) else { + Err(node)? + }; + let Table { children, imports, .. } = table; + if let Some(children) = children.get(&node) { + imports.entry(target).or_default().extend(children); + } + Ok(()) + } diff --git a/compiler/cl-typeck/src/import.rs b/compiler/cl-typeck/src/stage/import.rs similarity index 55% rename from compiler/cl-typeck/src/import.rs rename to compiler/cl-typeck/src/stage/import.rs index 0f9f79a..aae9e9a 100644 --- a/compiler/cl-typeck/src/import.rs +++ b/compiler/cl-typeck/src/stage/import.rs @@ -7,20 +7,32 @@ use crate::{ }; use cl_ast::{PathPart, Sym, Use, UseTree}; use core::slice; -use std::mem; +use std::{collections::HashSet, mem}; + +type Seen = HashSet; pub fn import<'a>(table: &mut Table<'a>) -> Vec<(Handle, Error<'a>)> { let pending = mem::take(&mut table.uses); + + let mut seen = Seen::new(); let mut failed = vec![]; for import in pending { - if let Err(e) = import_one(table, import) { - failed.push((import, e)); + let Err(e) = import_one(table, import, &mut seen) else { + continue; + }; + if let Error::NotFound(_, _) = e { + table.mark_use_item(import) } + failed.push((import, e)); } failed } -fn import_one<'a>(table: &mut Table<'a>, item: Handle) -> UseResult<'a, ()> { +fn import_one<'a>(table: &mut Table<'a>, item: Handle, seen: &mut Seen) -> UseResult<'a, ()> { + if !seen.insert(item) { + return Ok(()); + } + let Some(NodeKind::Use) = table.kind(item) else { Err(Error::ItsNoUse)? }; @@ -35,8 +47,13 @@ fn import_one<'a>(table: &mut Table<'a>, item: Handle) -> UseResult<'a, ()> { }; let Use { absolute, tree } = tree; - eprintln!("Resolving import {item}: {code}"); - import_tree(table, if !absolute { dst } else { table.root() }, dst, tree) + import_tree( + table, + if !absolute { dst } else { table.root() }, + dst, + tree, + seen, + ) } fn import_tree<'a>( @@ -44,32 +61,48 @@ fn import_tree<'a>( src: Handle, dst: Handle, tree: &UseTree, + seen: &mut Seen, ) -> UseResult<'a, ()> { match tree { - UseTree::Tree(trees) => { - for tree in trees { - import_tree(table, src, dst, tree)? - } - Ok(()) - } + UseTree::Tree(trees) => trees + .iter() + .try_for_each(|tree| import_tree(table, src, dst, tree, seen)), UseTree::Path(part, rest) => { let source = table .nav(src, slice::from_ref(part)) .ok_or_else(|| Error::NotFound(src, part.clone()))?; - import_tree(table, source, dst, rest) + import_tree(table, source, dst, rest, seen) } - UseTree::Alias(src_name, dst_name) => import_name(table, src, src_name, dst, dst_name), - UseTree::Name(src_name) => import_name(table, src, src_name, dst, src_name), - UseTree::Glob => import_glob(table, src, dst), + UseTree::Alias(src_name, dst_name) => { + import_name(table, src, src_name, dst, dst_name, seen) + } + UseTree::Name(src_name) => import_name(table, src, src_name, dst, src_name, seen), + UseTree::Glob => import_glob(table, src, dst, seen), } } -fn import_glob<'a>(table: &mut Table<'a>, src: Handle, dst: Handle) -> UseResult<'a, ()> { +fn import_glob<'a>( + table: &mut Table<'a>, + src: Handle, + dst: Handle, + seen: &mut Seen, +) -> UseResult<'a, ()> { let Table { children, imports, .. } = table; - if let Some(children) = children.get(&src) { - // TODO: check for new imports clobbering existing imports - imports.entry(dst).or_default().extend(children); + + if let Some(c) = children.get(&src) { + imports.entry(dst).or_default().extend(c) } + + import_deps(table, src, seen)?; + + let Table { imports, .. } = table; + + // Importing imports requires some extra work, since we can't `get_many_mut` + if let Some(i) = imports.get(&src) { + let uses: Vec<_> = i.iter().map(|(&k, &v)| (k, v)).collect(); + imports.entry(dst).or_default().extend(uses); + } + Ok(()) } @@ -79,7 +112,9 @@ fn import_name<'a>( src_name: &Sym, dst: Handle, dst_name: &Sym, + seen: &mut Seen, ) -> UseResult<'a, ()> { + import_deps(table, src, seen)?; match table.get_by_sym(src, src_name) { // TODO: check for new imports clobbering existing imports Some(src_id) => table.add_import(dst, *dst_name, src_id), @@ -88,6 +123,17 @@ fn import_name<'a>( Ok(()) } +/// Imports the dependencies of this node +fn import_deps<'a>(table: &mut Table<'a>, node: Handle, seen: &mut Seen) -> UseResult<'a, ()> { + if let Some(items) = table.use_items.get(&node) { + let out = items.clone(); + for item in out { + import_one(table, item, seen)?; + } + } + Ok(()) +} + pub type UseResult<'a, T> = Result>; #[derive(Debug)] diff --git a/compiler/cl-typeck/src/inference.rs b/compiler/cl-typeck/src/stage/infer.rs similarity index 100% rename from compiler/cl-typeck/src/inference.rs rename to compiler/cl-typeck/src/stage/infer.rs diff --git a/compiler/cl-typeck/src/populate.rs b/compiler/cl-typeck/src/stage/populate.rs similarity index 97% rename from compiler/cl-typeck/src/populate.rs rename to compiler/cl-typeck/src/stage/populate.rs index bb248ba..c8a0d3e 100644 --- a/compiler/cl-typeck/src/populate.rs +++ b/compiler/cl-typeck/src/stage/populate.rs @@ -15,9 +15,9 @@ pub struct Populator<'t, 'a> { impl<'t, 'a> Populator<'t, 'a> { pub fn new(table: &'t mut Table<'a>) -> Self { - Self { inner: table.root_handle_mut(), name: None } + Self { inner: table.root_entry_mut(), name: None } } - /// Constructs a new Populator with the provided parent DefID + /// Constructs a new Populator with the provided parent Handle pub fn with_id(&mut self, parent: Handle) -> Populator<'_, 'a> { Populator { inner: self.inner.with_id(parent), name: None } } diff --git a/compiler/cl-typeck/src/table.rs b/compiler/cl-typeck/src/table.rs index fb3ba92..3df9934 100644 --- a/compiler/cl-typeck/src/table.rs +++ b/compiler/cl-typeck/src/table.rs @@ -1,4 +1,29 @@ -//! Conlang's symbol table +//! The [Table] is a monolithic data structure representing everything the type checker +//! knows about a program. +//! +//! Individual nodes in the table can be queried using the [Entry] API ([Table::entry]) +//! or modified using the [EntryMut] API ([Table::entry_mut]). +//! +//! # Contents of a "node" +//! Always present: +//! - [NodeKind]: Determines how this node will be treated during the [stages](crate::stage) of +//! compilation +//! - [Parent node](Handle): Arranges this node in the hierarchical graph structure +//! +//! Populated as needed: +//! - Children: An associative array of [names](Sym) to child nodes in the graph. Child nodes are +//! arranged in a *strict* tree structure, with no back edges +//! - Imports: An associative array of [names](Sym) to other nodes in the graph. Not all import +//! nodes are back edges, but all back edges *must be* import nodes. +//! - [Types](TypeKind): Contains type information populated through type checking and inference. +//! Nodes with unpopulated types may be considered type variables in the future. +//! - [Spans][span]: Positional information from the source text. See [cl_structures::span]. +//! - [Metas](Meta): Metadata decorators. These may have an effect throughout the compiler. +//! - [Sources](Source): Pointers back into the AST, for future analysis. +//! - Impl Targets: Sparse mapping of `impl` nodes to their corresponding targets. +//! - etc. +//! +//! [span]: struct@Span use crate::{ entry::{Entry, EntryMut}, @@ -12,6 +37,10 @@ use std::collections::HashMap; // TODO: Cycle detection external to this module +/// The table is a monolithic data structure representing everything the type checker +/// knows about a program. +/// +/// See [module documentation](self). #[derive(Debug)] pub struct Table<'a> { root: Handle, @@ -20,13 +49,16 @@ pub struct Table<'a> { parents: IndexMap, pub(crate) children: HashMap>, pub(crate) imports: HashMap>, + pub(crate) use_items: HashMap>, types: HashMap, spans: HashMap, metas: HashMap, sources: HashMap>, - // code: HashMap, // TODO: lower sources + // code: HashMap, // TODO: lower sources impl_targets: HashMap, anon_types: HashMap, + + // --- Queues for algorithms --- pub(crate) impls: Vec, pub(crate) uses: Vec, } @@ -44,6 +76,7 @@ impl<'a> Table<'a> { parents, children: HashMap::new(), imports: HashMap::new(), + use_items: HashMap::new(), types: HashMap::new(), spans: HashMap::new(), metas: HashMap::new(), @@ -78,6 +111,8 @@ impl<'a> Table<'a> { } pub fn mark_use_item(&mut self, item: Handle) { + let parent = self.parents[item]; + self.use_items.entry(parent).or_default().push(item); self.uses.push(item); } @@ -85,14 +120,18 @@ impl<'a> Table<'a> { self.impls.push(item); } - /// Returns handles to all nodes sequentially by [DefID] - pub fn debug_handle_iter(&self) -> impl Iterator> { + pub fn handle_iter(&mut self) -> impl Iterator { + self.kinds.keys() + } + + /// Returns handles to all nodes sequentially by [Entry] + pub fn debug_entry_iter(&self) -> impl Iterator> { self.kinds.keys().map(|key| key.to_entry(self)) } - /// Gets the [DefID] of an anonymous type with the provided [TypeKind]. + /// Gets the [Handle] of an anonymous type with the provided [TypeKind]. /// If not already present, a new one is created. - pub fn anon_type(&mut self, kind: TypeKind) -> Handle { + pub(crate) fn anon_type(&mut self, kind: TypeKind) -> Handle { if let Some(id) = self.anon_types.get(&kind) { return *id; } @@ -103,11 +142,11 @@ impl<'a> Table<'a> { entry } - pub const fn root_handle(&self) -> Entry<'_, 'a> { + pub const fn root_entry(&self) -> Entry<'_, 'a> { self.root.to_entry(self) } - pub fn root_handle_mut(&mut self) -> crate::entry::EntryMut<'_, 'a> { + pub fn root_entry_mut(&mut self) -> crate::entry::EntryMut<'_, 'a> { self.root.to_entry_mut(self) } diff --git a/compiler/cl-typeck/src/type_kind.rs b/compiler/cl-typeck/src/type_kind.rs index c9eb8a3..bdca147 100644 --- a/compiler/cl-typeck/src/type_kind.rs +++ b/compiler/cl-typeck/src/type_kind.rs @@ -1,20 +1,17 @@ +//! A [TypeKind] is a node in the [Table](crate::table::Table)'s type graph + use crate::handle::Handle; use cl_ast::{Sym, Visibility}; use std::{fmt::Debug, str::FromStr}; mod display; -pub enum EntryKind { - Variable(Option), - Operator(TypeKind), -} - /// A [TypeKind] represents an item -/// (a component of a [Project](crate::project::Project)). +/// (a component of a [Table](crate::table::Table)) #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum TypeKind { /// An alias for an already-defined type - Alias(Handle), + Instance(Handle), /// A primitive type, built-in to the compiler Intrinsic(Intrinsic), /// A user-defined aromatic data type @@ -42,10 +39,6 @@ pub enum TypeKind { pub enum Adt { /// A union-like enum type Enum(Vec<(Sym, Option)>), - /// A C-like enum - CLikeEnum(Vec<(Sym, u128)>), - /// An enum with no fields, which can never be constructed - FieldlessEnum, /// A structural product type with named members Struct(Vec<(Sym, Visibility, Handle)>), diff --git a/compiler/cl-typeck/src/type_kind/display.rs b/compiler/cl-typeck/src/type_kind/display.rs index dd665c6..898e944 100644 --- a/compiler/cl-typeck/src/type_kind/display.rs +++ b/compiler/cl-typeck/src/type_kind/display.rs @@ -8,7 +8,7 @@ use std::fmt::{self, Display, Write}; impl Display for TypeKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TypeKind::Alias(def) => write!(f, "alias to #{def}"), + TypeKind::Instance(def) => write!(f, "alias to #{def}"), TypeKind::Intrinsic(i) => i.fmt(f), TypeKind::Adt(a) => a.fmt(f), TypeKind::Ref(cnt, def) => { @@ -47,14 +47,6 @@ impl Display for Adt { }) })(f.delimit_with("enum {", "}")) } - Adt::CLikeEnum(variants) => { - let mut variants = variants.iter(); - separate(", ", || { - let (name, descrim) = variants.next()?; - Some(move |f: &mut Delimit<_>| write!(f, "{name} = {descrim}")) - })(f.delimit_with("enum {", "}")) - } - Adt::FieldlessEnum => write!(f, "enum"), Adt::Struct(members) => { let mut members = members.iter(); separate(", ", || { diff --git a/compiler/cl-typeck/src/type_resolver.rs b/compiler/cl-typeck/src/type_resolver.rs deleted file mode 100644 index e1eef5c..0000000 --- a/compiler/cl-typeck/src/type_resolver.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Performs step 2 of type checking: Evaluating type definitions - -use crate::{ - definition::{Adt, Def, DefKind, TypeKind, ValueKind}, - handle::DefID, - node::{Node, NodeSource}, - project::{evaluate::EvaluableTypeExpression, Project as Prj}, -}; -use cl_ast::*; - -/// Evaluate a single ID -pub fn resolve(prj: &mut Prj, id: DefID) -> Result<(), &'static str> { - let Def { node: Node { kind: Some(source), meta, .. }, kind: DefKind::Undecided, .. } = prj[id] - else { - return Ok(()); - }; - - let kind = match &source { - NodeSource::Root => "root", - NodeSource::Alias(_) => "type", - NodeSource::Module(_) => "mod", - NodeSource::Enum(_) => "enum", - NodeSource::Variant(_) => "variant", - NodeSource::Struct(_) => "struct", - NodeSource::Const(_) => "const", - NodeSource::Static(_) => "static", - NodeSource::Function(_) => "fn", - NodeSource::Impl(_) => "impl", - NodeSource::Use(_) => "use", - NodeSource::Local(_) => "let", - NodeSource::Ty(_) => "ty", - }; - let name = prj[id].name().unwrap_or("".into()); - - eprintln!("Resolver: \x1b[32mEvaluating\x1b[0m \"\x1b[36m{kind} {name}\x1b[0m\" (`{id:?}`)"); - - for Meta { name, kind } in meta { - if let ("intrinsic", MetaKind::Equals(Literal::String(s))) = (&**name, kind) { - prj[id].kind = DefKind::Type(TypeKind::Intrinsic( - s.parse().map_err(|_| "Failed to parse intrinsic")?, - )); - } - } - if DefKind::Undecided == prj[id].kind { - prj[id].kind = source.resolve_type(prj, id)?; - } - - eprintln!("\x1b[33m=> {}\x1b[0m", prj[id].kind); - - Ok(()) -} - -/// Resolves a given node -pub trait TypeResolvable<'a> { - /// The return type upon success - type Out; - /// Resolves type expressions within this node - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result; -} - -impl<'a> TypeResolvable<'a> for NodeSource<'a> { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - match self { - NodeSource::Root => Ok(DefKind::Type(TypeKind::Module)), - NodeSource::Module(v) => v.resolve_type(prj, id), - NodeSource::Alias(v) => v.resolve_type(prj, id), - NodeSource::Enum(v) => v.resolve_type(prj, id), - NodeSource::Variant(v) => v.resolve_type(prj, id), - NodeSource::Struct(v) => v.resolve_type(prj, id), - NodeSource::Const(v) => v.resolve_type(prj, id), - NodeSource::Static(v) => v.resolve_type(prj, id), - NodeSource::Function(v) => v.resolve_type(prj, id), - NodeSource::Local(v) => v.resolve_type(prj, id), - NodeSource::Impl(v) => v.resolve_type(prj, id), - NodeSource::Use(v) => v.resolve_type(prj, id), - NodeSource::Ty(v) => v.resolve_type(prj, id), - } - } -} - -impl<'a> TypeResolvable<'a> for &'a Meta { - type Out = DefKind; - - #[allow(unused_variables)] - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let Meta { name, kind } = self; - match (name.as_ref(), kind) { - ("intrinsic", MetaKind::Equals(Literal::String(intrinsic))) => Ok(DefKind::Type( - TypeKind::Intrinsic(intrinsic.parse().map_err(|_| "unknown intrinsic type")?), - )), - (_, MetaKind::Plain) => Ok(DefKind::Type(TypeKind::Intrinsic( - name.parse().map_err(|_| "Unknown intrinsic type")?, - ))), - _ => Err("Unknown meta attribute"), - } - } -} - -impl<'a> TypeResolvable<'a> for &'a Module { - type Out = DefKind; - #[allow(unused_variables)] - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - Ok(DefKind::Type(TypeKind::Module)) - } -} - -impl<'a> TypeResolvable<'a> for &'a Alias { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - let alias = if let Some(ty) = &self.from { - Some( - ty.evaluate(prj, parent) - .or_else(|_| ty.evaluate(prj, id)) - .map_err(|_| "Unresolved type in alias")?, - ) - } else { - None - }; - - Ok(DefKind::Type(TypeKind::Alias(alias))) - } -} - -impl<'a> TypeResolvable<'a> for &'a Enum { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let Enum { name: _, kind } = self; - let EnumKind::Variants(v) = kind else { - return Ok(DefKind::Type(TypeKind::Adt(Adt::FieldlessEnum))); - }; - let mut fields = vec![]; - for Variant { name, kind: _ } in v { - let id = prj[id].module.get_type(*name); - fields.push((*name, id)) - } - Ok(DefKind::Type(TypeKind::Adt(Adt::Enum(fields)))) - } -} - -impl<'a> TypeResolvable<'a> for &'a Variant { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - // Get the grandparent of this node, for name resolution - let parent = prj.parent_of(id).unwrap_or(id); - let grandparent = prj.parent_of(parent).unwrap_or(parent); - let Variant { name: _, kind } = self; - - Ok(DefKind::Type(match kind { - VariantKind::Plain => return Ok(DefKind::Type(TypeKind::Empty)), - VariantKind::CLike(_) => return Ok(DefKind::Undecided), - VariantKind::Tuple(ty) => match &ty.kind { - TyKind::Empty => TypeKind::Tuple(vec![]), - TyKind::Tuple(TyTuple { types }) => { - TypeKind::Tuple(types.evaluate(prj, grandparent).map_err(|e| { - eprintln!("{e}"); - "" - })?) - } - _ => Err("Unexpected TyKind in tuple variant")?, - }, - VariantKind::Struct(members) => { - TypeKind::Adt(Adt::Struct(members.resolve_type(prj, parent)?)) - } - })) - } -} - -impl<'a> TypeResolvable<'a> for &'a Struct { - type Out = DefKind; - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - let Struct { name: _, kind } = self; - Ok(match kind { - StructKind::Empty => DefKind::Type(TypeKind::Empty), - StructKind::Tuple(types) => DefKind::Type(TypeKind::Adt(Adt::TupleStruct({ - let mut out = vec![]; - for ty in types { - out.push(( - Visibility::Public, - ty.evaluate(prj, parent) - .map_err(|_| "Unresolved type in tuple-struct member")?, - )); - } - out - }))), - StructKind::Struct(members) => { - DefKind::Type(TypeKind::Adt(Adt::Struct(members.resolve_type(prj, id)?))) - } - }) - } -} - -impl<'a> TypeResolvable<'a> for &'a StructMember { - type Out = (Sym, Visibility, DefID); - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - let StructMember { name, vis, ty } = self; - - let ty = ty - .evaluate(prj, parent) - .map_err(|_| "Invalid type while resolving StructMember")?; - - Ok((*name, *vis, ty)) - } -} - -impl<'a> TypeResolvable<'a> for &'a Const { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let Const { ty, .. } = self; - let ty = ty - .evaluate(prj, id) - .map_err(|_| "Invalid type while resolving const")?; - Ok(DefKind::Value(ValueKind::Const(ty))) - } -} - -impl<'a> TypeResolvable<'a> for &'a Static { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - let Static { ty, .. } = self; - let ty = ty - .evaluate(prj, parent) - .map_err(|_| "Invalid type while resolving static")?; - Ok(DefKind::Value(ValueKind::Static(ty))) - } -} - -impl<'a> TypeResolvable<'a> for &'a Function { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - let Function { sign, .. } = self; - let sign = sign - .evaluate(prj, parent) - .map_err(|_| "Invalid type in function signature")?; - Ok(DefKind::Value(ValueKind::Fn(sign))) - } -} - -impl<'a> TypeResolvable<'a> for &'a Let { - type Out = DefKind; - - #[allow(unused)] - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let Let { mutable, name, ty, init } = self; - Ok(DefKind::Undecided) - } -} - -impl<'a> TypeResolvable<'a> for &'a Impl { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let parent = prj.parent_of(id).unwrap_or(id); - - let target = match &self.target { - ImplKind::Type(t) => t.evaluate(prj, parent), - ImplKind::Trait { for_type, .. } => for_type.evaluate(prj, parent), - } - .map_err(|_| "Unresolved type in impl target")?; - - match prj.pool.get_many_mut([id, target]) { - // TODO: Better error handling - Err(_) => Err(concat!( - file!(), - line!(), - column!(), - "id and target are same" - ))?, - Ok([id, target]) => { - for (name, def) in &id.module.types { - target.module.insert_type(*name, *def); - } - for (name, def) in &id.module.values { - target.module.insert_value(*name, *def); - } - } - } - Ok(DefKind::Impl(target)) - } -} - -impl<'a> TypeResolvable<'a> for &'a Use { - type Out = DefKind; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - todo!("Resolve types for {self} with ID {id} in {prj:?}") - } -} - -impl<'a> TypeResolvable<'a> for &'a TyKind { - type Out = DefKind; - - #[allow(unused)] - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - todo!() - } -} - -impl<'a, T> TypeResolvable<'a> for &'a [T] -where &'a T: TypeResolvable<'a> -{ - type Out = Vec<<&'a T as TypeResolvable<'a>>::Out>; - - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - let mut members = vec![]; - for member in self { - members.push(member.resolve_type(prj, id)?); - } - Ok(members) - } -} - -impl<'a, T> TypeResolvable<'a> for Option<&'a T> -where &'a T: TypeResolvable<'a> -{ - type Out = Option<<&'a T as TypeResolvable<'a>>::Out>; - fn resolve_type(self, prj: &mut Prj<'a>, id: DefID) -> Result { - match self { - Some(t) => Some(t.resolve_type(prj, id)).transpose(), - None => Ok(None), - } - } -} diff --git a/compiler/cl-typeck/src/use_importer.rs b/compiler/cl-typeck/src/use_importer.rs deleted file mode 100644 index ffb1f6c..0000000 --- a/compiler/cl-typeck/src/use_importer.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! WIP use-item importer. This performs eager import resolution on the AST -//! -//! # TODOs: -//! - [ ] Resolve imports using a graph traversal rather than linear iteration -//! - [ ] Separate imported items from natively declared items -//! - [ ] Separate the use-import pass from the project -//! - [ ] Report errors in a meaningful way -//! - [ ] Lazy import resolution using graph-edge traversal during name lookup? -//! - It doesn't seem to me like the imports in a given scope *can change*. - -#![allow(unused)] -use std::fmt::format; - -use cl_ast::*; - -use crate::{ - definition::{Def, DefKind}, - handle::DefID, - node::NodeSource, - project::Project, -}; - -type UseResult = Result<(), String>; - -impl<'a> Project<'a> { - pub fn resolve_imports(&mut self) -> UseResult { - for id in self.pool.keys() { - self.visit_def(id)?; - } - Ok(()) - } - - pub fn visit_def(&mut self, id: DefID) -> UseResult { - let Def { kind, node, module } = &self.pool[id]; - if let (DefKind::Use(parent), Some(NodeSource::Use(u))) = (kind, node.kind) { - println!("Importing use item {u}"); - self.visit_use(u, *parent); - } - Ok(()) - } - - pub fn visit_use(&mut self, u: &'a Use, parent: DefID) -> UseResult { - let Use { absolute, tree } = u; - - self.visit_use_tree(tree, parent, if *absolute { self.root } else { parent }) - } - - pub fn visit_use_tree(&mut self, tree: &'a UseTree, parent: DefID, c: DefID) -> UseResult { - match tree { - UseTree::Tree(trees) => { - for tree in trees { - self.visit_use_tree(tree, parent, c)?; - } - } - UseTree::Path(part, rest) => { - let c = self.evaluate(part, c)?; - self.visit_use_tree(rest, parent, c)?; - } - - UseTree::Name(name) => self.visit_use_leaf(name, parent, c)?, - UseTree::Alias(from, to) => self.visit_use_alias(from, to, parent, c)?, - UseTree::Glob => self.visit_use_glob(parent, c)?, - } - Ok(()) - } - - pub fn visit_use_path(&mut self) -> UseResult { - Ok(()) - } - - pub fn visit_use_leaf(&mut self, name: &'a Sym, parent: DefID, c: DefID) -> UseResult { - self.visit_use_alias(name, name, parent, c) - } - - pub fn visit_use_alias( - &mut self, - from: &Sym, - name: &Sym, - parent: DefID, - c: DefID, - ) -> UseResult { - let mut imported = false; - let c_mod = &self[c].module; - let (tid, vid) = ( - c_mod.types.get(from).copied(), - c_mod.values.get(from).copied(), - ); - let parent = &mut self[parent].module; - - if let Some(tid) = tid { - parent.types.insert(*name, tid); - imported = true; - } - - if let Some(vid) = vid { - parent.values.insert(*name, vid); - imported = true; - } - - if imported { - Ok(()) - } else { - Err(format!("Identifier {name} not found in module {c}")) - } - } - - pub fn visit_use_glob(&mut self, parent: DefID, c: DefID) -> UseResult { - // Loop over all the items in c, and add them as items in the parent - if parent == c { - return Ok(()); - } - let [parent, c] = self - .pool - .get_many_mut([parent, c]) - .expect("parent and c are not the same"); - - for (k, v) in &c.module.types { - parent.module.types.entry(*k).or_insert(*v); - } - for (k, v) in &c.module.values { - parent.module.values.entry(*k).or_insert(*v); - } - Ok(()) - } -} diff --git a/stdlib/test.cl b/stdlib/test.cl index a779453..7bebee8 100644 --- a/stdlib/test.cl +++ b/stdlib/test.cl @@ -27,7 +27,7 @@ enum Hodgepodge { fn noop () -> bool { loop if false { - + } else break loop if false { } else break loop if false { @@ -62,3 +62,18 @@ fn if_else() -> i32 { } // block 4 } + +mod horrible_imports { + mod foo { + use super::{bar::*, baz::*}; + struct Foo(&Foo, &Bar) + } + mod bar { + use super::{foo::*, baz::*}; + struct Bar(&Foo, &Baz) + } + mod baz { + use super::{foo::*, bar::*}; + struct Baz(&Foo, &Bar) + } +}