Repository navigation
What counts as the same code
dejadoc compares canonical forms built by syn-canon. Two pieces of code share a form when they differ only in the ways this page lists. Anything else keeps them apart.
Whitespace, comments, line breaks and documentation never matter. Local names do not matter either. Every name a piece of code declares becomes a number in the order it appears: let bindings, parameters, the function's own name, generics, lifetimes, labels, local items, use aliases and macro_rules! names.
fn area(width: u32, height: u32) -> u32 { width * height }
fn product(a: u32, b: u32) -> u32 { a * b }These two functions match. Names the code uses without declaring them stay as written, such as called functions, fields, methods, types from elsewhere and string literals. So a.len() and a.count() stay different, and so do "x" and "y".
r#name and name count as the same name. Self in a type position stands for the impl self type.
These rewrites apply wherever they keep the meaning. Each row shows two spellings that match.
| Written | Matches |
|---|---|
use std::{io, fmt}; |
use std::fmt; use std::io; |
0 => { 1 }, in a match
|
0 => 1 |
else { if c { … } } |
else if c { … } |
return x; at the end of a body or a tail match arm |
x |
(x), -> (), ;;
|
x, nothing, ;
|
fn f<T: Clone>(t: T) |
fn f<T>(t: T) where T: Clone |
&'_ T, Foo<'_>, &'static str in a const
|
&T, Foo, &str
|
x: Vec::<u8> as a type, a trailing + in bounds |
x: Vec<u8>, no +
|
self: &Self, self: Self
|
&self, self
|
-0i8 |
0i8 |
println!("{}", x) |
println!("{x}") |
vec![1, 2,] |
vec![1, 2] |
0x10 |
16 |
#[derive(Debug)] #[derive(Clone)] |
#[derive(Clone, Debug)] |
#[doc], lint levels such as #[allow(…)], #[rustfmt::skip]
|
nothing |
pub(in crate) |
pub(crate) |
Some folds only hold for code that compiles, so they apply only outside compile_fail doctests. These drop mut from a binding that is a whole pattern, drop the semicolon after a non-tail block statement, drop an empty else {}, turn a const block that holds one scalar literal into that literal, and substitute private usize constants used only as array lengths. They also read extern fn as extern "C" fn, return () as return, and a pattern with a leading | as one without it.
Attribute payloads and the input of procedural macros and derives stay exactly as written, because the macro reads them. A #[doc] whose value is a macro call also stays, since it runs at compile time.
For code that compiles, dejadoc also matches code that computes the same thing in a different shape. Each rewrite applies only when it can prove the result is the same, for example because every value involved is a primitive integer or bool.
| Rewrite | Example pair |
|---|---|
| Literal integer arithmetic |
(2u8 + 3u8) * 4u8 and 20u8
|
| Bitwise algebra |
(a ^ 1u32) ^ b and b ^ (1u32 ^ a)
|
| Repeated operands |
a | a and a for a bool
|
| Comparison direction |
a >= b and b <= a
|
| Boolean logic |
!(!!p && (q == true)) and !p || !q
|
| Opposite branches |
if !q { 1u8 } else { 2u8 } and if q { 2u8 } else { 1u8 }
|
| Temporaries and aliases |
let m = i & 15u32; let c = m; c ^ 3u32 and (i & 15u32) ^ 3u32
|
| Unused bindings |
let dead = 17u32; removed |
| Independent statements |
let a = low(x); let b = high(x); in either order, when low and high are local helpers over primitives |
| Struct fields |
S { x: 1u32, y: 2u32 } and S { y: 2u32, x: 1u32 }
|
| Pattern alternatives |
1 | 2 => … and 2 | 1 => …
|
Item order and impl blocks |
two impl S blocks and one impl S holding both methods |
Arithmetic that overflows, divides by zero or depends on pointer-sized types keeps its expression. So (255u8 + 1u8) - 1u8 does not match 255u8. Floating point, overloaded operators, unknown macros and code whose side effects could be observed also keep their written shape.
None of these rewrites apply to compile_fail doctests, because the point of such a test is the exact code that fails.
A match means the code shares a form after the listed rewrites. dejadoc reads syntax without compiling the scanned project, so matching copies can still differ in receiver types, inferred types or lifetimes, unresolved imports, macro expansion, enclosing cfg or control-flow targets. Dependency candidates also require compatible signatures and an available dependency edge. Dependency reuse explains those checks.