c2rust refactor provides a general-purpose rewriting command, rewrite_expr,for transforming expressions.In its most basic form, rewrite_expr replaces one expression with another,everywhere in the crate:
1. rewrite_expr '1+1' '2'

Here, all instances of the expression 1+1 (the "pattern") are replaced with2 (the "replacement").
rewrite_expr parses both the pattern and the replacement as Rust expressions,and compares the structure of the expression instead of its raw text whenlooking for occurrences of the pattern. This lets it recognize that 1 + 1and 1 + / comment / both match the pattern 1+1 (despite being textuallydistinct), while 1+11 does not (despite being textually similar).
Metavariables
In rewriteexpr's expression pattern, any name beginning with doubleunderscores is a _metavariable. Just as a variable in an ordinary Rustmatch expression will match any value (and bind it for later use), ametavariable in an expression pattern will match any Rust code. For example,the expression pattern __x + 1 will match any expression that adds 1 tosomething:
1. rewrite_expr '__x + 1' '11'

In these examples, the __x metavariable matches the expressions 1, 2 * 3,and f().
Using bindings
When a metavariable matches against some piece of code, the code it matches isbound to the variable for later use. Specifically, rewrite_expr'sreplacement argument can refer back to those metavariables to substitute in thematched code:
1. rewrite_expr '__x + 1' '11 * __x'

In each case, the expression bound to the __x metavariable is substitutedinto the right-hand side of the multiplication in the replacement.
Multiple occurences
Finally, the same metavariable can appear multiple times in the pattern. Inthat case, the pattern matches only if each occurence of the metavariablematches the same expression. For example:
1. rewrite_expr '__x + __x' '2 * __x'

Here a + a and f() + f() are both replaced, but f() + 1 is not because__x cannot match both f() and 1 at the same time.
Example: adding a function argument
Suppose we wish to add an argument to an existing function. All currentcallers of the function should pass a default value of 0 for this newargument. We can update the existing calls like this:
1. rewrite_expr 'my_func(__x, __y)' 'my_func(__x, __y, 0)'

Every call to my_func now passes a third argument, and we can update thedefinition of my_func to match.
Special matching forms
rewriteexpr supports several _special matching forms that can appear inpatterns to add extra restrictions to matching.
def!
A pattern such as def!(::foo::f) matches any ident or path expression thatresolves to the function whose absolute path is ::foo::f. For example, toreplace all expressions referencing the function foo::f with ones referencingfoo::g:
1. rewrite_expr 'def!(::foo::f)' '::foo::g'

This works for all direct references to f, whether by relative path(foo::f), absolute path (::foo::f), or imported identifier (just f, withuse foo::f in scope). It can even handle imports under a different name(f2 with use foo::f as f2 in scope), since it checks only the path of thereferenced definition, not the syntax used to reference it.
Under the hood
When rewrite_expr attempts to match def!(path) against some expression e,it actually completely ignores the content of e itself. Instead, it performsthese steps:
- Check
rustc's name resolution results to find the definitiondthateresolves to. (Ifedoesn't resolve to a definition, then the matchingfails.) - Construct an absolute path
dpathreferring tod. For definitions inthe current crate, this path looks like::mod1::def1. For definitions inother crates, it looks like::crate1::mod1::def1. - Match
dpathagainst thepathpattern provided as the argumentofdef!. Thenematchesdef!(path)ifdpathmatchespath, andfails to match otherwise.[
Debugging match failures
Matching with def! can sometimes fail in surprising ways, since theuser-provided path is matched against a generated path that may not appearexplicitly anywhere in the source code. For example, this attempt to matchHashMap::new does not succeed:
1. rewrite_expr
2. 'def!(::std::collections::hash_map::HashMap::new)()'
3. '::std::collections::hash_map::HashMap::with_capacity(10)'

The debug_match_expr command exists to diagnose such problems. It takes onlya pattern, and prints information about attempts to match it at various pointsin the crate:
1. debug_match_expr 'def!(::std::collections::hash_map::HashMap::new)()'
Here, its output includes this line:
1. def!(): trying to match pattern path(::std::collections::hash_map::HashMap::new) against AST path(::std::collections::HashMap::new)
Which reveals the problem: the absolute path def! generates forHashMap::new uses the reexport at std::collections::HashMap, not thecanonical definition at std::collections::hash_map::HashMap. Updating theprevious rewrite_expr command allows it to succeed:
1. rewrite_expr
2. 'def!(::std::collections::HashMap::new)()'
3. '::std::collections::HashMap::with_capacity(10)'

Metavariables
The argument to def! is a path pattern, which can contain metavariables justlike the overall expression pattern. For instance, we can rewrite all calls tofunctions from the foo module:
1. rewrite_expr 'def!(::foo::__name)()' '123'

Since every definition in the foo module has an absolute path of the form::foo::(something), they all match the expression patterndef!(::foo::__name).
Like any other metavariable, the ones in a def! path pattern can be used inthe replacement expression to substitute in the captured name. For example, wecan replace all references to items in the foo module with references to thesame-named items in the bar module:
1. rewrite_expr 'def!(::foo::__name)' '::bar::__name'

Note, however, that each metavariable in a path pattern can match only a singleident. This means foo::name will not match the path to an item in asubmodule, such as foo::one::two. Handling these would require an additionalrewrite step, such as rewrite_expr 'def!(::foo::name1::name2)' '::bar::name1::__name2'.
typed!
A pattern of the form typed!(e, ty) matches any expression that matches thepattern e, but only if the type of that expression matches the pattern ty.For example, we can perform a rewrite that only affects i32s:
1. rewrite_expr 'typed!(__e, i32)' '0'

Every expression matches the metavariable __e, but only the i32s (whetherliterals or variables of type i32) are affected by the rewrite.
Under the hood
Internally, typed! works much like def!. To match an expression eagainst typed!(e_pat, ty_pat), rewrite_expr follows these steps:
- Consult
rustc's typechecking results to get the type ofe. Callthat typerustc_ty. rustc_tyis an internal, abstract representation of the type, which isnot suitable for matching. Construct a concrete representation ofrustc_ty, and call itty.- Match
eagainste_patandtyagainstty_pat. Thenematchestyped!(e_pat, ty_pat)if both matches succeed, and fails to matchotherwise.[
Debugging match failures
When matching fails unexpectedly, debug_match_expr is once again useful forunderstanding the problem. For example, this rewriting command has no effect:
1. rewrite_expr "typed!(__e, &'static str)" '"hello"'

Passing the same pattern to debug_match_expr produces output that includesthe following:
1. typed!(): trying to match pattern type(&'static str) against AST type(&str)
Now the problem is clear: the concrete type representation constructed formatching omits lifetimes. Replacing &'static str with &str in the patterncauses the rewrite to succeed:
1. rewrite_expr 'typed!(__e, &str)' '"hello"'

Metavariables
The expression pattern and type pattern arguments of typed!(e, ty) arehandled using the normal rewrite_expr matching engine, which means they cancontain metavariables and other special matching forms. For example,metavariables can capture both parts of the expression and parts of its typefor use in the replacement:
1. rewrite_expr
2. 'typed!(Vec::with_capacity(__n), ::std::vec::Vec<__ty>)'
3. '::std::iter::repeat(<__ty>::default())
4. .take(__n)
5. .collect::<Vec<__ty>>()'

Notice that the rewritten code has the correct element type in the call todefault, even in cases where the type is not written explicitly in theoriginal expression! The matching of typed! obtains the inferred typeinformation from rustc, and those inferred types are captured bymetavariables in the type pattern.
Example: transmute to <*const T>::as_ref
This example demonstrates usage of def! and typed!.
Suppose we have some unsafe code that uses transmute to convert a rawpointer that may be null (const T) into an optional reference(Option<&T>). This conversion is better expressed using the as_ref methodof const T, and we'd like to apply this transformation automatically.
Initial attempt
Here is a basic first attempt:
1. rewrite_expr 'transmute(__e)' '__e.as_ref()'

This has two major shortcomings, which we will address in order:
- It works only on code that calls exactly
transmute(foo). The instances thatimportstd::memand callmem::transmute(foo)do not get rewritten. - It rewrites transmutes between any types, not just
*const TtoOption<&T>. Only transmutes between those types should be replaced withas_ref.[
Identifying transmute calls with def!
We want to rewrite calls to std::mem::transmute, regardless of how thosecalls are written. This is a perfect use case for def!:
1. rewrite_expr 'def!(::std::intrinsics::transmute)(__e)' '__e.as_ref()'

Now our rewrite catches all uses of transmute, whether they're written astransmute(foo), mem::transmute(foo), or even ::std::mem::transmute(foo).
Notice that we refer to transmute as std::intrinsics::transmute: this isthe location of its original definition, which is re-exported in std::mem.See the "def!: debugging match failures" sectionfor an explanation of how we discovered this.
Filtering transmute calls by type
We now have a command for rewriting all transmute calls, but we'd like it torewrite only transmutes from *const T to Option<&T>. We can achieve thisby filtering the input and output types with typed!:
1. rewrite_expr '
2. typed!(
3. def!(::std::intrinsics::transmute)(
4. typed!(__e, *const __ty)
5. ),
6. ::std::option::Option<&__ty>
7. )
8. ' '__e.as_ref()'

Now only those transmutes that turn const T into Option<&T> are affectedby the rewrite. And because typed! has access to the results of typeinference, this works even on transmute calls that are not fully annotated(transmute(foo), not just transmute::<const T, Option<&T>>(foo)).
marked!
The marked! form is simple: marked!(e, label) matches an expression only ife matches the expression and the expression is marked with the given label.See the documentation on marks and select for moreinformation.
Other commands
Several other refactoring commands use the same pattern-matching engine asrewrite_expr:
rewrite_ty PAT REPL(docs) works likerewrite_expr,except it matches and replaces type annotations instead of expressions.abstract SIG PAT(docs) replaces expressions matching apattern with calls to a newly-created function.type_fix_rules(docs) uses type patterns to findthe appropriate rule to fix each type error.select'smatchexpr*(docs) and similar filtersuse syntax patterns to identify nodes to mark.
