Thank you for your interest in contributing to this Lean 4 reimplementation in Rust!
The core is functionally complete but has borrow checker issues. Here's how to fix them:
In the whnf method around line 129, change:
// ❌ This causes borrow errors:
let kind = arena.kind(term).ok_or_else(|| { ... })?;
// ✅ Change to:
let kind = arena.kind(term).ok_or_else(|| { ... })?.clone();Do the same for is_def_eq_whnf method around line 217.
Then update all pattern matches to use the cloned values instead of references:
// Change: *func -> func
// Change: *arg -> arg
// Change: *body -> bodySame pattern in solve_unify method around line 162:
let kind1 = arena.kind(t1)?.clone();
let kind2 = arena.kind(t2)?.clone();Update references in pattern matches.
# Find unused imports and variables
cargo clippy -p leanr-core
# Fix them:
# - Remove unused imports
# - Prefix unused vars with _underscorecargo test -p leanr-core --libAll tests should pass once borrowing is fixed.
Create the lexer and parser:
// leanr-syntax/src/token.rs
pub enum Token {
// Keywords
Def, Theorem, Axiom, Inductive,
// Symbols
Colon, Assign, Arrow,
// Literals
Ident(String),
Number(u64),
// ...
}
// leanr-syntax/src/lexer.rs
pub struct Lexer { ... }
impl Lexer {
pub fn next_token(&mut self) -> Token { ... }
}
// leanr-syntax/src/parser.rs
pub struct Parser { ... }
impl Parser {
pub fn parse_expr(&mut self) -> Expr { ... }
pub fn parse_decl(&mut self) -> Decl { ... }
}
// leanr-syntax/src/ast.rs
pub enum Expr {
Var(String),
App(Box<Expr>, Box<Expr>),
Lam(String, Box<Expr>),
// ...
}Tests to write:
- Lex basic identifiers
- Lex keywords
- Parse simple expressions
- Parse function definitions
- Error recovery
Transform AST to typed core terms:
pub struct Elaborator {
arena: Arena,
env: Environment,
unifier: Unifier,
// ...
}
impl Elaborator {
pub fn elaborate_expr(
&mut self,
expr: &ast::Expr,
expected: Option<TermId>
) -> Result<TermId> {
match expr {
ast::Expr::Var(name) => {
// Look up in context
// Insert implicit arguments
// ...
}
// ...
}
}
}Key features:
- Implicit argument insertion
- Type inference
- Metavariable creation
- Error messages with spans
Generate recursors for inductive types:
pub fn check_positivity(
ind: &InductiveDecl
) -> Result<()> {
// Ensure no bad recursive occurrences
}
pub fn generate_recursor(
ind: &InductiveDecl
) -> Declaration {
// Create eliminator with correct type
}
pub fn compile_match(
cases: &[MatchCase]
) -> TermId {
// Lower to recursor application
}WebAssembly bindings:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct LeanEnvironment {
inner: Environment,
}
#[wasm_bindgen]
impl LeanEnvironment {
#[wasm_bindgen(constructor)]
pub fn new() -> Self { ... }
pub fn add_definition(
&mut self,
name: &str,
ty: &str,
body: &str
) -> Result<(), JsValue> {
// Parse, elaborate, check, add
}
pub fn check(&self, expr: &str) -> Result<String, JsValue> {
// Type check expression
}
}Build with:
wasm-pack build leanr-wasm --target webEvery module should have tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_functionality() {
// Arrange
let mut arena = Arena::new();
// Act
let result = arena.mk_var(0);
// Assert
assert_eq!(arena.terms(), 1);
}
}In tests/ directory:
// tests/type_checking.rs
#[test]
fn test_identity_function() {
let src = "def id (α : Type) (x : α) : α := x";
// Parse, elaborate, check
assert!(result.is_ok());
}Use quickcheck or proptest:
#[quickcheck]
fn substitution_preserves_typing(term: TermId) -> bool {
// If Γ, x:A ⊢ t : B and Γ ⊢ u : A
// Then Γ ⊢ t[x:=u] : B
}cargo fmtcargo clippy -- -D warnings/// Brief description
///
/// Longer explanation with examples:
///
/// ```
/// use leanr_core::Arena;
/// let arena = Arena::new();
/// ```
///
/// # Panics
/// Never panics
///
/// # Errors
/// Returns `Err` if ...
pub fn important_function() -> Result<()> {
// ...
}eprintln!("Term: {:#?}", arena.kind(term));// In conversion.rs whnf():
eprintln!("Reducing: {:?}", term);
let result = /* ... */;
eprintln!("Result: {:?}", result);let stats = arena.stats();
println!("Cache hit rate: {:.2}%",
stats.cache_hits as f64 /
(stats.cache_hits + stats.cache_misses) as f64 * 100.0
);- ✅ Use hash-consing (it's already there)
- ✅ Cache expensive computations
- ✅ Use
Vec::with_capacitywhen size is known - ✅ Profile before optimizing
- ❌ Clone large structures unnecessarily
- ❌ Use
Stringwhere&strsuffices - ❌ Allocate in hot loops
- ❌ Guess at optimizations
# Install
cargo install cargo-flamegraph
# Profile
cargo flamegraph -p leanr-core --bench conversion
# View flamegraph.svgBefore submitting PR:
- Code compiles without warnings
- All tests pass
- New tests added for new features
- Documentation updated
-
cargo fmtapplied -
cargo clippyclean - No
unsafecode (unless justified) - Error messages are helpful
- Performance impact considered
- Questions? Open an issue with [question] tag
- Bug? Include minimal reproduction
- Feature? Discuss design first
By contributing, you agree that your contributions will be licensed under Apache 2.0.
Happy hacking! 🚀