Skip to content

Commit

Permalink
nr: Cleanup scoper, add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
CohenArthur committed Oct 15, 2023
1 parent f1e975c commit 4210213
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 38 deletions.
39 changes: 38 additions & 1 deletion name_resolve/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
//! The name-resolve module takes care of resolving each "name" within a `jinko` program to
//! its definition site. You can think of it as a function, which will take an unresolved
//! [`Fir`] as input, and output a new [`Fir`] where each node points to the definition it
//! uses. To do this, multiple passes are applied on the input [`Fir`].
//!
//! 1. We start by "scoping" *all* of the definitions and usages in the program.
//! This is done via the [`Scoper`] type, which will assign an enclosing scope to
//! each node of our [`Fir`]. This is important, since name resolution in `jinko`
//! can go "backwards" or "upwards". You can access a definition in an *outer*
//! scope from an *inner* scope. This means that the outermost scope is able to
//! use definitions from the outermost scope. But if this "enclosing" or "parent"
//! relationship does not exist, then the usage is invalid:
//!
//! ```ignore
//! {
//! func foo() {}
//! }
//! {
//! { foo() }
//! // this scope does not have `foo`'s scope as a parent, so it will need to error out later on
//! }
//! ```
//!
//! 2. We collect all of the definitions within the program using the [`Declarator`] struct.
//! A definition can be a function definition, a new type, as well as a new binding. This
//! "definition collection" pass will only error out if a definition is present twice, e.g.:
//!
//! ```ignore
//! func foo() {}
//! func foo(different: int, arguments: int) -> string { "oh no" }
//! ```
//!
//! 2. Finally, we resolve all *usages* to their *definitions* using the [`Resolver`] type.
//! If a usage cannot be resolved to a definition, then it is an error. Similarly, if a usage
//! can be resolved to more than one definitions, we error out. The resolver does not take care
//! of resolving complex usages, such as methods, generic function calls or specialization.
use std::{collections::HashMap, ops::Index};

use error::{ErrKind, Error};
Expand Down Expand Up @@ -273,7 +310,7 @@ impl<'enclosing> NameResolveCtx<'enclosing> {
let root = fir.nodes.last_key_value().unwrap();

let mut scoper = Scoper {
current_scope: *root.0,
current_scope: scoper::Scope(*root.0),
enclosing_scope: HashMap::new(),
};

Expand Down
72 changes: 35 additions & 37 deletions name_resolve/src/scoper.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
// FIXME: Documentation

use std::collections::HashMap;
use std::{collections::HashMap, mem};

use fir::{Fallible, Fir, Kind, Node, OriginIdx, RefIdx, Traversal};
use flatten::FlattenData;

pub(crate) struct Scope(pub(crate) OriginIdx);

impl Scope {
pub fn replace(&mut self, new: OriginIdx) -> OriginIdx {
mem::replace(&mut self.0, new)
}

pub fn origin(&self) -> OriginIdx {
self.0
}
}

pub(crate) struct Scoper {
pub(crate) current_scope: OriginIdx, // TODO: Wrap in a new type with a .replace() method
pub(crate) current_scope: Scope,
pub(crate) enclosing_scope: HashMap<OriginIdx, OriginIdx>,
}

impl Scoper {
/// Set the enclosing scope of `to_scope` to the current scope
fn scope(&mut self, to_scope: &Node<FlattenData>) {
self.enclosing_scope
.insert(to_scope.origin, self.current_scope);
.insert(to_scope.origin, self.current_scope.origin());
}

/// Enter a new scope, replacing the context's current scope. This returns the old scope,
/// which you will need to reuse when you exit the scoped node you are visiting
fn enter_scope(&mut self, new_scope: OriginIdx) -> OriginIdx {
self.current_scope.replace(new_scope)
}

fn maybe_visit_child(&mut self, fir: &Fir<FlattenData<'_>>, ref_idx: &RefIdx) -> Fallible<()> {
Expand All @@ -24,26 +40,11 @@ impl Scoper {
RefIdx::Unresolved => Ok(()),
}
}

fn visit_field_instantiation(
&mut self,
fir: &Fir<FlattenData<'_>>,
ref_idx: &RefIdx,
) -> Fallible<()> {
self.maybe_visit_child(fir, ref_idx)

// let field_instantiation = dbg!(&fir[ref_idx]);

// if let Kind::TypedValue { value: _, ty } = field_instantiation.kind {
// // do not visit the `value` - it's always unresolved at this point
// self.maybe_visit_child(fir, &ty)
// } else {
// unreachable!()
// }
}
}

impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {
impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok to have void as an error type? */>
for Scoper
{
fn traverse_assignment(
&mut self,
fir: &Fir<FlattenData<'ast>>,
Expand All @@ -64,9 +65,7 @@ impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {
return_ty: &Option<RefIdx>,
block: &Option<RefIdx>,
) -> Fallible<()> {
// TODO: Factor in a function
let old_scope = self.current_scope;
self.current_scope = node.origin;
let old = self.enter_scope(node.origin);

generics
.iter()
Expand All @@ -78,7 +77,7 @@ impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {
block.map(|definition| self.maybe_visit_child(fir, &definition));
return_ty.map(|ty| self.maybe_visit_child(fir, &ty));

self.current_scope = old_scope;
self.enter_scope(old);

Ok(())
}
Expand All @@ -89,14 +88,14 @@ impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {
node: &Node<FlattenData<'ast>>,
stmts: &[RefIdx],
) -> Fallible<()> {
let old_scope = self.current_scope;
self.current_scope = node.origin;
// TODO: Ugly but can we do anything better? Can we have types which force you to exit a scope if you enter one?
let old = self.enter_scope(node.origin);

stmts
.iter()
.for_each(|stmt| self.maybe_visit_child(fir, stmt).unwrap());

self.current_scope = old_scope;
self.enter_scope(old);

Ok(())
}
Expand All @@ -119,16 +118,15 @@ impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {
self.maybe_visit_child(fir, ty)
}
Kind::Type { fields, .. } => {
fields.iter().for_each(|field| {
// Factor in a function?
let old_scope = self.current_scope;
self.current_scope = node.origin;
let old = self.enter_scope(node.origin);

fields.iter().for_each(|field| {
// FIXME: Is unwrap okay here?
self.maybe_visit_child(fir, field).unwrap();

self.current_scope = old_scope;
});

self.enter_scope(old);

Ok(())
}
Kind::Generic { default } => default
Expand All @@ -147,7 +145,7 @@ impl<'ast> Traversal<FlattenData<'ast>, () /* FIXME: Ok? */> for Scoper {

fields
.iter()
.for_each(|field| self.visit_field_instantiation(fir, field).unwrap());
.for_each(|field| { self.maybe_visit_child(fir, field) }.unwrap());

Ok(())
}
Expand Down

0 comments on commit 4210213

Please sign in to comment.