Molt is a syntax-aware search and replace tool for Rust. Molt is experimental software.
Molt provides a simple pattern-matching language for the Rust programming language. It's particularly useful for mechanical refactorings, API migrations and pattern finding across large code bases.
Suppose we have a main.rs containing:
fn main() {
let x = foo.get("key").insert("value");
let y = bar.get_map().get(CustomEnum::Key).insert(10);
}Suppose we want to replace .get(key).insert(val) with a single method call insert_at_key(key, val).
We write the following molt file:
merge_fns.molt:
fn main(input: Expr) {
let key: Expr;
let val: Expr;
let expr: Expr;
let { $expr.get($key).insert($val) } = input;
input -> { $expr.insert_at_key($key, $val) };
}Running molt merge_fns.molt in the rust project will result in:
main.rs, after:
fn main() {
let x = foo.insert_at_key("key", "value");
let y = bar.get_map().insert_at_key(CustomEnum::Key, 10);
}Molt being aims to make it easy to express complex replacement logic.
For example, the following rule rewrites foo(); statements inside functions named do_something, while leaving other foo(); statements untouched.
fn main(input: Fn) {
if let { do_something } = input.name {
for stmt in input.stmts {
if let { foo(); } = stmt {
stmt -> { bar(); };
}
}
}
}Input Rust:
fn do_something() {
foo();
some_other_fn();
let x = 3;
foo();
}
fn some_other_fn() {
foo();
}After running Molt:
fn do_something() {
bar();
some_other_fn();
let x = 3;
bar();
}
fn some_other_fn() {
foo();
}Molt can bind a list of syntax nodes and then inspect or rewrite each element.
This rule matches an array behind a reference, prints the matched list, and
replaces each element with 5:
fn main(input: Expr) {
let list: List<Expr>;
let { [$list] } = input;
print(list);
for item in list {
print(item);
item -> { 5 };
}
}Input Rust:
const X: &[usize] = &[1, 2, 3];After running Molt:
const X: &[usize] = &[5, 5, 5];Printed output:
[1, 2, 3]
1
2
3
To install, use cargo install:
cargo install --git https://github.com/tehforsch/moltThis will install the molt binary in your Cargo bin directory (usually ~/.cargo/bin).
A Molt file is a small program that runs against Rust syntax nodes.
Molts entry point is the main function, which takes a syntax node as an argument
fn main(input: Expr) { }Alternatively, the main function can be omitted, but a input variable needs to be present and its type needs to be annotated:
let input: Expr;Patterns are written as Rust syntax inside { }. Patterns can be matched/destructured with the let statement, which takes a pattern on the left-hand side and a syntax node expression on the right-hand side:
let input: Expr;
let x: Expr;
let y: Expr;
let { $x + $y } = input;The let statement checks for matches against the right hand side and binds the LHS variables to the matched nodes if a match was found. If no match was found, the let statement implicitly returns from the current function, so none of the checks below the let statement will take place if a single let fails.
let input: Expr;
let value: Expr;
if let { Some($value) } = input {
print(value);
}Variables inside Rust patterns are referenced with $name. If the variable has
not been declared yet, a type annotation which declares its syntactic kind needs
to be present before the match:
let input: Expr;
let receiver: Expr;
let key: Expr;
if let { $receiver.get($key) } = input {
print(key);
}Use target -> { replacement }; to replace a matched syntax node:
let input: Expr;
let arg: Expr;
if let { old_name($arg) } = input {
input -> { new_name($arg) };
}Some syntactic nodes offer their sub-nodes via field access:
let input: Fn;
if let { foo } = input.name {
input.generics -> { <T> };
}Use List<T> to bind multiple syntax nodes at the same level.
The for loop iterates over items of a list.
fn main(input: Fn) {
let args: List<FnArg>;
let name: Ident;
let stmts: List<Stmt>;
let {
fn $name($args) {
$stmts
}
} = input;
for arg in args {
print(arg);
}
}Molt currently has a small set of builtin functions.
print simply prints the given variable / syntax node:
let input: Fn;
print(input.name);fn example() {}example
The dbg builtin prints the given variable along with some surrounding context
let input: Fn;
dbg(input.name);fn example() {}note:
┌─ test input:1:4
│
1 │ fn example() {}
│ ^^^^^^^
The following kinds are currently supported.
Matches any identifier.
let input: Ident;
print(input);fn main() {
let x = 3;
}main
x
Matches any literal value.
let input: Lit;
print(input)fn main() {
let x = 3;
let y = "foo";
}3
"foo"
Matches any Rust expression. Note that this will often recursively expressions along with their subexpressions:
let input: Expr;
print(input);fn main() {
let x = 3 + 3;
}3
3
3 + 3
Matches any Rust statement. Examples:
let input: Stmt;
if let { let foo = 3; } = input {
input -> { let foo = 4; };
}fn main() {
let foo = 3;
let bar = 5;
}fn main() {
let foo = 4;
let bar = 5;
}Matches any type. Examples:
fn main(input: Type) {
let ty: Type;
if let { Vec<$ty> } = input {
input -> { Box<[$ty]> };
}
}Matches any rust pattern in match expressions or destructuring. Examples:
let input: Pat;
let pat: Pat;
if let { Some($pat) } = input {
input -> { $pat };
}fn main() {
if let Some("bar") = foo() {
println!("baz");
}
}fn main() {
if let "bar" = foo() {
println!("baz");
}
}Matches match expression arms. Examples:
let input: Arm;
let pat: Pat;
let val: Expr;
if let { $pat => Ok($val) } = input {
input.body -> { $val };
}fn main() {
match x {
MyEnum::A => Ok(1),
MyEnum::B => Ok(2),
MyEnum::C => Ok(3),
}
}fn main() {
match x {
MyEnum::A => 1,
MyEnum::B => 2,
MyEnum::C => 3,
}
}Matches struct field definitions. Examples:
let input: Field;
print(input);struct Foo {
a: A,
b: B,
c: C,
}a: A
b: B
c: C
Matches visibility modifiers. Examples:
let input: Vis;
if let { pub } = input {
input -> { pub(crate) };
}pub fn foo() { }
pub(crate) fn bar() { }pub(crate) fn foo() { }
pub(crate) fn bar() { }Matches generic parameter lists. Examples:
let input: Generics;
print(input);fn foo<A, B, C: std::fmt::Debug>() { }<A, B, C: std::fmt::Debug>
Matches top-level Rust items such as functions, trait definitions, use statements, constants. Examples:
let input: Item;
print(input);const X: usize = 3;
fn foo() { }const X: usize = 3;
fn foo() { }
The Rust parser for this project is adapted from syn.