Skip to content

What counts as the same code

LucaCappelletti94 edited this page Oct 19, 2026 · 2 revisions

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.

Formatting and names

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.

Style folds

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.

Proven rewrites

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.

What can still differ

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.

Clone this wiki locally