Skip to content

Commit

Permalink
chore(experimental): Add Elaborator pass (#4992)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #4953

## Summary\*

Adds an elaborator - a concept borrowed from dependently typed languages
- which performs name resolution and type checking simultaneously. The
new elaborator is currently disabled (inaccessible from user code) since
it is not hooked up to the `dc_crate` module.

This PR is extremely large (sorry!) but is 99% a pseudo copy-paste of
existing code from the name resolver and type checker, only slightly
adapted to be able to work in tandem. Unfortunately, this also means
that we'll need to duplicate any changes made to the name resolver &
type checker in the elaborator as well until we can remove the old name
resolution and type checking passes. For example,
#4958 will need to be integrated
into the elaborator as well.

## Additional Context

After this PR the plan is to
1. Connect the elaborator to `dc_crate`, enabling it with a command-line
option.
2. Insert logic for the comptime scanning pass in the elaborator as
well.
3. Test the elaborator against all existing tests.
4. Once it passes, we can use the elaborator by default and delete the
name resolution, type checking, and comptime scanning passes.

## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [ ] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
jfecher authored May 9, 2024
1 parent 95d4d13 commit e12bf9b
Show file tree
Hide file tree
Showing 15 changed files with 3,170 additions and 22 deletions.
5 changes: 3 additions & 2 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ impl ForRange {
identifier: Ident,
block: Expression,
for_loop_span: Span,
) -> StatementKind {
) -> Statement {
/// Counter used to generate unique names when desugaring
/// code in the parser requires the creation of fresh variables.
/// The parser is stateless so this is a static global instead.
Expand Down Expand Up @@ -662,7 +662,8 @@ impl ForRange {
let block = ExpressionKind::Block(BlockExpression {
statements: vec![let_array, for_loop],
});
StatementKind::Expression(Expression::new(block, for_loop_span))
let kind = StatementKind::Expression(Expression::new(block, for_loop_span));
Statement { kind, span: for_loop_span }
}
}
}
Expand Down
604 changes: 604 additions & 0 deletions compiler/noirc_frontend/src/elaborator/expressions.rs

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#![allow(unused)]
use std::{
collections::{BTreeMap, BTreeSet},
rc::Rc,
};

use crate::graph::CrateId;
use crate::hir::def_map::CrateDefMap;
use crate::{
ast::{
ArrayLiteral, ConstructorExpression, FunctionKind, IfExpression, InfixExpression, Lambda,
UnresolvedTraitConstraint, UnresolvedTypeExpression,
},
hir::{
def_collector::dc_crate::CompilationError,
resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext},
scope::ScopeForest as GenericScopeForest,
type_check::TypeCheckError,
},
hir_def::{
expr::{
HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression,
HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression,
HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression,
HirMethodReference, HirPrefixExpression,
},
traits::TraitConstraint,
},
macros_api::{
BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression,
HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression,
MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement,
StatementKind, StructId,
},
node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId},
Shared, StructType, Type, TypeVariable,
};

mod expressions;
mod patterns;
mod scope;
mod statements;
mod types;

use fm::FileId;
use iter_extended::vecmap;
use noirc_errors::{Location, Span};
use regex::Regex;
use rustc_hash::FxHashSet as HashSet;

/// ResolverMetas are tagged onto each definition to track how many times they are used
#[derive(Debug, PartialEq, Eq)]
struct ResolverMeta {
num_times_used: usize,
ident: HirIdent,
warn_if_unused: bool,
}

type ScopeForest = GenericScopeForest<String, ResolverMeta>;

struct Elaborator {
scopes: ScopeForest,

errors: Vec<CompilationError>,

interner: NodeInterner,
file: FileId,

in_unconstrained_fn: bool,
nested_loops: usize,

/// True if the current module is a contract.
/// This is usually determined by self.path_resolver.module_id(), but it can
/// be overridden for impls. Impls are an odd case since the methods within resolve
/// as if they're in the parent module, but should be placed in a child module.
/// Since they should be within a child module, in_contract is manually set to false
/// for these so we can still resolve them in the parent module without them being in a contract.
in_contract: bool,

/// Contains a mapping of the current struct or functions's generics to
/// unique type variables if we're resolving a struct. Empty otherwise.
/// This is a Vec rather than a map to preserve the order a functions generics
/// were declared in.
generics: Vec<(Rc<String>, TypeVariable, Span)>,

/// When resolving lambda expressions, we need to keep track of the variables
/// that are captured. We do this in order to create the hidden environment
/// parameter for the lambda function.
lambda_stack: Vec<LambdaContext>,

/// Set to the current type if we're resolving an impl
self_type: Option<Type>,

/// The current dependency item we're resolving.
/// Used to link items to their dependencies in the dependency graph
current_item: Option<DependencyId>,

trait_id: Option<TraitId>,

path_resolver: Rc<dyn PathResolver>,
def_maps: BTreeMap<CrateId, CrateDefMap>,

/// In-resolution names
///
/// This needs to be a set because we can have multiple in-resolution
/// names when resolving structs that are declared in reverse order of their
/// dependencies, such as in the following case:
///
/// ```
/// struct Wrapper {
/// value: Wrapped
/// }
/// struct Wrapped {
/// }
/// ```
resolving_ids: BTreeSet<StructId>,

trait_bounds: Vec<UnresolvedTraitConstraint>,

current_function: Option<FuncId>,

/// All type variables created in the current function.
/// This map is used to default any integer type variables at the end of
/// a function (before checking trait constraints) if a type wasn't already chosen.
type_variables: Vec<Type>,

/// Trait constraints are collected during type checking until they are
/// verified at the end of a function. This is because constraints arise
/// on each variable, but it is only until function calls when the types
/// needed for the trait constraint may become known.
trait_constraints: Vec<(TraitConstraint, ExprId)>,
}

impl Elaborator {
fn elaborate_function(&mut self, function: NoirFunction, _id: FuncId) {
// This is a stub until the elaborator is connected to dc_crate
match function.kind {
FunctionKind::LowLevel => todo!(),
FunctionKind::Builtin => todo!(),
FunctionKind::Oracle => todo!(),
FunctionKind::Recursive => todo!(),
FunctionKind::Normal => {
let _body = self.elaborate_block(function.def.body);
}
}
}

fn push_scope(&mut self) {
// stub
}

fn pop_scope(&mut self) {
// stub
}

fn push_err(&mut self, error: impl Into<CompilationError>) {
self.errors.push(error.into());
}
}
Loading

0 comments on commit e12bf9b

Please sign in to comment.