Skip to content

avoid_equal_expressions

v0.8.0 Warning Code Quality

This rule flags a binary expression whose left and right operands are textually identical — a == a, flag && flag, total - total.

These are typos with a constant result. One side was meant to be a different variable, field, or index, and the mistake is invisible: the code compiles, the analyzer is silent, and the expression quietly always evaluates the same way.

This rule is in the core preset and takes no configuration.

The classic: a hand-written operator == where one side of a field comparison was never changed. Unequal points now compare equal.

class Point {
const Point(this.x, this.y);
final int x;
final int y;
@override
bool operator ==(Object other) {
if (other is! Point) return false;
// `other.y` was meant on the right — this ignores y entirely.
return x == other.x && y == y;
}
@override
int get hashCode => Object.hash(x, y);
}
class Point {
const Point(this.x, this.y);
final int x;
final int y;
@override
bool operator ==(Object other) {
if (other is! Point) return false;
return x == other.x && y == other.y;
}
@override
int get hashCode => Object.hash(x, y);
}

Copy-pasting a condition and forgetting to change the second half:

bool canPublish(bool isDraft, bool isApproved) {
// Reported — this is just `!isDraft`; isApproved is never consulted.
return !isDraft && !isDraft;
}
bool canPublish(bool isDraft, bool isApproved) => !isDraft && isApproved;
bool isInWindow(int start, int end, int value) {
// Reported — `start >= start` is always true.
return value >= start && start >= start;
}
bool isInWindow(int start, int end, int value) =>
value >= start && value <= end;
int remaining(int quota, int used) {
// Reported — `quota - quota` is 0 whatever the quota is.
return quota - quota;
}
int remaining(int quota, int used) => quota - used;

Only operators where identical operands are meaningless are reported: ==, !=, <, <=, >, >=, &&, ||, -, /, ~/, %, ??. Arithmetic like a + a and a * a is ordinary and never flagged.

Two exemptions keep the rule quiet on deliberate code:

NaN checks. value != value is the canonical NaN test, so a self-comparison with == or != is skipped whenever the operand’s static type is double, num, or their nullable forms.

// Not reported — this is the NaN test.
bool isNotANumber(double value) => value != value;

Side-effecting operands. Only plain reads — identifiers, property access, indexing, literals — are compared. A call may legitimately differ between invocations:

// Not reported — two calls to next() can return different values.
bool sameTwice(Iterator<int> it) => it.moveNext() == it.moveNext();

This rule is in the core preset, so it is on with preset: core, preset: recommended or preset: opinionated.

To turn it off:

many_lints.yaml
rules:
avoid_equal_expressions: false

To keep the rule on but skip certain paths, use per-rule exclude.