Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monomorphize generic types #660

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
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