Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fir/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
mod mapper;
mod multi_mapper;
mod traverse;
mod tree_like;

pub use mapper::Mapper;
pub use multi_mapper::MultiMapper;
pub use traverse::Traversal;
pub use tree_like::TreeLike;

use crate::Fir;

Expand Down
38 changes: 18 additions & 20 deletions fir/src/iter/multi_mapper.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use crate::{Fir, Incomplete, Kind, Node, OriginIdx, RefIdx};

pub trait MultiMapper<T, U: Default + From<T>, E> {
/// Each implementer of [`MultiMapper`] should keep its own [`OriginIdx`] counter in order to supply the [`Fir`]
/// with new nodes. This can be done by keeping an [`OriginIdx`] as part of the context, and repeatedly
/// calling [`OriginIdx::next`] on it.
fn next_origin(&mut self) -> OriginIdx;

pub trait MultiMapper<T, U: From<T>, E> {
fn map_constant(
&mut self,
_data: T,
Expand Down Expand Up @@ -285,20 +280,23 @@ pub trait MultiMapper<T, U: Default + From<T>, E> {
/// all valid mapped nodes. This allows an interpreter to keep trying
/// passes and emit as many errors as possible
fn multi_map(&mut self, fir: Fir<T>) -> Result<Fir<U>, Incomplete<U, E>> {
let (fir, errs) = fir.nodes.into_values().fold(
(Fir::default(), Vec::new()),
|(new_fir, mut errs), node| match self.map_node(node) {
Ok(nodes) => nodes
.into_iter()
.fold((new_fir, errs), |(new_fir, errs), node| {
(new_fir.append(node), errs)
}),
Err(e) => {
errs.push(e);
(new_fir, errs)
}
},
);
let (fir, errs) =
fir.nodes
.into_values()
.fold(
(Fir::new(), Vec::new()),
|(new_fir, mut errs), node| match self.map_node(node) {
Ok(nodes) => nodes
.into_iter()
.fold((new_fir, errs), |(new_fir, errs), node| {
(new_fir.append(node), errs)
}),
Err(e) => {
errs.push(e);
(new_fir, errs)
}
},
);

if errs.is_empty() {
Ok(fir)
Expand Down
203 changes: 203 additions & 0 deletions fir/src/iter/tree_like.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use crate::{Fir, Kind, Node, OriginIdx, RefIdx};

// FIXME: Should we still be able to emit errors?
pub trait TreeLike<T> {
fn visit_many(&mut self, fir: &Fir<T>, many: &[RefIdx]) {
many.iter().for_each(|r| self.visit_reference(fir, r))
}

fn visit_optional(&mut self, fir: &Fir<T>, reference: &Option<RefIdx>) {
if let Some(r) = reference {
self.visit_reference(fir, r)
}
}

fn visit_reference(&mut self, fir: &Fir<T>, reference: &RefIdx) {
self.visit(fir, &reference.expect_resolved())
}

fn visit_constant(&mut self, fir: &Fir<T>, _node: &Node<T>, c: &RefIdx) {
self.visit_reference(fir, c)
}

fn visit_type_reference(&mut self, fir: &Fir<T>, _node: &Node<T>, to: &RefIdx) {
self.visit_reference(fir, to)
}

fn visit_typed_value(&mut self, fir: &Fir<T>, _node: &Node<T>, value: &RefIdx, ty: &RefIdx) {
self.visit_reference(fir, value);
self.visit_reference(fir, ty);
}

fn visit_generic(&mut self, fir: &Fir<T>, _node: &Node<T>, default: &Option<RefIdx>) {
self.visit_optional(fir, default)
}

fn visit_record_type(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
generics: &[RefIdx],
fields: &[RefIdx],
) {
self.visit_many(fir, generics);
self.visit_many(fir, fields);
}

fn visit_union_type(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
generics: &[RefIdx],
variants: &[RefIdx],
) {
self.visit_many(fir, generics);
self.visit_many(fir, variants);
}

fn visit_function(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
generics: &[RefIdx],
args: &[RefIdx],
return_type: &Option<RefIdx>,
block: &Option<RefIdx>,
) {
self.visit_many(fir, generics);
self.visit_many(fir, args);
self.visit_optional(fir, return_type);
self.visit_optional(fir, block);
}

fn visit_binding(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
to: &Option<RefIdx>,
ty: &Option<RefIdx>,
) {
self.visit_optional(fir, to);
self.visit_optional(fir, ty);
}

fn visit_assignment(&mut self, fir: &Fir<T>, _node: &Node<T>, to: &RefIdx, from: &RefIdx) {
self.visit_reference(fir, to);
self.visit_reference(fir, from);
}

fn visit_instantiation(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
to: &RefIdx,
generics: &[RefIdx],
fields: &[RefIdx],
) {
self.visit_reference(fir, to);
self.visit_many(fir, generics);
self.visit_many(fir, fields);
}

fn visit_type_offset(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
instance: &RefIdx,
field: &RefIdx,
) {
self.visit_reference(fir, instance);
self.visit_reference(fir, field);
}

fn visit_call(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
to: &RefIdx,
generics: &[RefIdx],
args: &[RefIdx],
) {
self.visit_reference(fir, to);
self.visit_many(fir, generics);
self.visit_many(fir, args);
}

fn visit_conditional(
&mut self,
fir: &Fir<T>,
_node: &Node<T>,
condition: &RefIdx,
true_block: &RefIdx,
false_block: &Option<RefIdx>,
) {
self.visit_reference(fir, condition);
self.visit_reference(fir, true_block);
self.visit_optional(fir, false_block);
}

fn visit_loop(&mut self, fir: &Fir<T>, _node: &Node<T>, condition: &RefIdx, block: &RefIdx) {
self.visit_reference(fir, condition);
self.visit_reference(fir, block);
}

fn visit_statements(&mut self, fir: &Fir<T>, _node: &Node<T>, stmts: &[RefIdx]) {
self.visit_many(fir, stmts)
}

fn visit_return(&mut self, fir: &Fir<T>, _node: &Node<T>, value: &Option<RefIdx>) {
self.visit_optional(fir, value)
}

fn visit_node_reference(&mut self, fir: &Fir<T>, _node: &Node<T>, to: &RefIdx) {
self.visit_reference(fir, to)
}

fn visit(&mut self, fir: &Fir<T>, start: &OriginIdx) {
let node = &fir[start];

match &node.kind {
Kind::Constant(c) => self.visit_constant(fir, node, c),
Kind::TypeReference(to) => self.visit_type_reference(fir, node, to),
Kind::NodeRef(to) => self.visit_node_reference(fir, node, to),
Kind::Generic { default } => self.visit_generic(fir, node, default),
Kind::RecordType { generics, fields } => {
self.visit_record_type(fir, node, generics, fields)
}
Kind::UnionType { generics, variants } => {
self.visit_union_type(fir, node, generics, variants)
}
Kind::Function {
generics,
args,
return_type,
block,
} => self.visit_function(fir, node, generics, args, return_type, block),
Kind::Binding { to, ty } => self.visit_binding(fir, node, to, ty),
Kind::Assignment { to, from } => self.visit_assignment(fir, node, to, from),
Kind::Instantiation {
to,
generics,
fields,
} => self.visit_instantiation(fir, node, to, generics, fields),
Kind::TypeOffset { instance, field } => {
self.visit_type_offset(fir, node, instance, field)
}
Kind::Call { to, generics, args } => self.visit_call(fir, node, to, generics, args),
Kind::Conditional {
condition,
true_block,
false_block,
} => self.visit_conditional(fir, node, condition, true_block, false_block),
Kind::Loop { condition, block } => self.visit_loop(fir, node, condition, block),
Kind::Statements(stmts) => self.visit_statements(fir, node, stmts),
Kind::Return(value) => self.visit_return(fir, node, value),
}
}

fn visit_all(&mut self, fir: &Fir<T>) {
fir.nodes
.last_key_value()
.map(|last| self.visit(fir, last.0));

Check failure on line 201 in fir/src/iter/tree_like.rs

View workflow job for this annotation

GitHub Actions / clippy

called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`

error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` --> fir/src/iter/tree_like.rs:199:9 | 199 | / fir.nodes 200 | | .last_key_value() 201 | | .map(|last| self.visit(fir, last.0)); | |________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::option_map_unit_fn)]` help: try | 199 ~ if let Some(last) = fir.nodes 200 + .last_key_value() { self.visit(fir, last.0) } |
}
}
8 changes: 8 additions & 0 deletions fir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,14 @@ impl<T> Fir<T> {
}
}

impl<T: Clone> Clone for Fir<T> {
fn clone(&self) -> Fir<T> {
Fir {
nodes: self.nodes.clone(),
}
}
}

// FIXME: The `pass` function should return an Incomplete Fir. How to make it so that we can chain Fir operations
// nicely?
pub trait Pass<T: Debug, U: Debug, E> {
Expand Down
2 changes: 1 addition & 1 deletion fire/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl<'ast, 'fir> Fire<'ast, 'fir> {
// TODO: Rename? `node`? `node_from_ref`? `rnode`? `ref`? `view`?
// should this return an `AccessedNode` or w/ever which we can then `.fire()`?
fn access(&self, r: &RefIdx) -> &'fir Node<FlattenData<'ast>> {
&self.fir.nodes[&r.expect_resolved()]
&self.fir[r]
}

#[must_use]
Expand Down
9 changes: 9 additions & 0 deletions name_resolve/src/declarator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,13 @@ impl<'ast> Traversal<FlattenData<'ast>, NameResolutionError> for Declarator<'_,
) -> Fallible<NameResolutionError> {
self.define(DefinitionKind::Binding, node)
}

fn traverse_generic(
&mut self,
_: &Fir<FlattenData<'ast>>,
node: &Node<FlattenData<'ast>>,
_: &Option<RefIdx>,
) -> Fallible<NameResolutionError> {
self.define(DefinitionKind::Type, node)
}
}
5 changes: 5 additions & 0 deletions typecheck/src/actual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl TypeLinkResolver<'_> {
/// Recursively try and resolve a type link within the type context. This will update the given node's type
/// within the type context.
fn resolve_link(&mut self, to_resolve: OriginIdx, fir: &Fir<FlattenData>) -> OriginIdx {
dbg!(to_resolve);
let ChainEnd {
intermediate_nodes,
final_type,
Expand Down Expand Up @@ -78,6 +79,10 @@ impl TypeLinkResolver<'_> {

self.find_end_inner(fir, ty_ref.expect_resolved(), intermediate_nodes)
}
TypeVariable::Generic(g) => ControlFlow::Break(ChainEnd {
intermediate_nodes,
final_type: Type::generic(*g),
}),
TypeVariable::Record(r) => {
// we don't insert here so that we can get the typeref directly later on - does that make sense?
// self.new.types.new_type(final_type.clone());
Expand Down
32 changes: 32 additions & 0 deletions typecheck/src/generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// TODO: Typer could also be responsible for building the list of monomorphization requests (and constraints?)
// so something like a constraints: Map<TypeToMono, Constraints>?
// -> this is good but it needs to be per invocation - so per function call/type instantiation
// -> so is it more like a Map<GenericCall, Constraints>?
// -> Then Checker will be responsible for checking that T: Constraints?
// -> this seems annoying to deal with
// a good API would be something like - ConstraintBuilder -> Checker -> Monormorphizer
// with Checker checking the constraints? does that make sense?
// do we want to go with an easier first step where we have ConstraintBuilder -> Monomorphizer -> Checker? And we
// don't need to add anything to Checker? Since all functions and types will already be mono'd. But then the errors
// will be shit so we might as well build the constraints from the get-go.
// Now do we want to instead have a ConstraintChecker? who just takes care of checking constraints? then Mono and we
// can probably pass all of that to checker anyway. This is probably a better split. In that case, we should move
// this over to a new module.

// ConstraintBuilder -> ConstraintMap
// ConstraintChecker(ConstraintMap) -> Result<MonoRequests>
// Monomorphizer(MonoRequests) -> Result<Fir> // infaillible?

// let subs = Substitutions::build();
// let monod = Monomorphize(subs).map(fir)?;
// let replaced = Replace(monod).map(fir)?;

mod constraint_builder;
mod mono;
mod replace;
mod substitutions;

pub use constraint_builder::ConstraintBuilder;
pub use mono::Monomorphize;
pub use replace::Replace;
pub use substitutions::Substitutions;
Loading
Loading