Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/readability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
AvoidUnconditionalPreprocessorIfCheck.cpp
BracesAroundStatementsCheck.cpp
ConstReturnTypeCheck.cpp
ConstantOperandOrderCheck.cpp
ContainerContainsCheck.cpp
ContainerDataPointerCheck.cpp
ContainerSizeEmptyCheck.cpp
Expand Down
197 changes: 197 additions & 0 deletions clang-tools-extra/clang-tidy/readability/ConstantOperandOrderCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "ConstantOperandOrderCheck.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

/// Out-of-line ctor so vtable is emitted.
ConstantOperandOrderCheck::ConstantOperandOrderCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {
// Read options (StringRef -> std::string).
PreferredSide = Options.get(PreferredSideOption, "Right").str();

// Parse BinaryOperators option (comma-separated).
std::string OpsCSV =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::string OpsCSV =
const std::string OpsCSV =

Options.get(BinaryOperatorsOption, "==,!=,<,<=,>,>=").str();

llvm::SmallVector<llvm::StringRef, 8> Tokens;
llvm::StringRef(OpsCSV).split(Tokens, ',');
Operators.clear();
for (auto Tok : Tokens) {
llvm::StringRef Trim = Tok.trim();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
llvm::StringRef Trim = Tok.trim();
const StringRef Trim = Tok.trim();

if (!Trim.empty())
Operators.emplace_back(Trim.str());
}
}

ConstantOperandOrderCheck::~ConstantOperandOrderCheck() = default;

void ConstantOperandOrderCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, PreferredSideOption, PreferredSide);
Options.store(Opts, BinaryOperatorsOption,
llvm::join(Operators.begin(), Operators.end(), ","));
}

// ------------------------ helpers ------------------------

namespace {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace {


static const Expr *strip(const Expr *E) {
return E ? E->IgnoreParenImpCasts() : nullptr;
}

static bool isSimpleConstantExpr(const Expr *E) {
E = strip(E);
if (!E)
return false;

if (isa<IntegerLiteral>(E) || isa<FloatingLiteral>(E) ||
isa<StringLiteral>(E) || isa<CharacterLiteral>(E) ||
isa<CXXBoolLiteralExpr>(E) || isa<CXXNullPtrLiteralExpr>(E) ||
isa<GNUNullExpr>(E))
return true;

if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
if (isa<EnumConstantDecl>(DRE->getDecl()))
return true;
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
return VD->isConstexpr() || VD->getType().isConstQualified();
}

return false;
}

static bool hasSideEffectsExpr(const Expr *E, ASTContext &Ctx) {
E = strip(E);
return E && E->HasSideEffects(Ctx);
}

static std::string invertOperatorText(llvm::StringRef Op) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static std::string invertOperatorText(llvm::StringRef Op) {
static StringRef invertOperatorText(llvm::StringRef Op) {

if (Op == "<")
return ">";
if (Op == ">")
return "<";
if (Op == "<=")
return ">=";
if (Op == ">=")
return "<=";
// symmetric: ==, !=
return Op.str();
}

} // namespace
Comment on lines +98 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} // namespace


// ------------------------ matchers ------------------------

void ConstantOperandOrderCheck::registerMatchers(MatchFinder *Finder) {
if (Operators.empty())
return;

for (const auto &Op : Operators) {
Finder->addMatcher(binaryOperator(hasOperatorName(Op),
hasLHS(expr().bind("lhs")),
hasRHS(expr().bind("rhs")))
.bind("binop"),
this);
}
}

// ------------------------ check / fixit ------------------------

void ConstantOperandOrderCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Bin = Result.Nodes.getNodeAs<BinaryOperator>("binop");
const auto *L = Result.Nodes.getNodeAs<Expr>("lhs");
const auto *R = Result.Nodes.getNodeAs<Expr>("rhs");
if (!Bin || !L || !R)
return;
Comment on lines +122 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be an assert


const ASTContext &Ctx = *Result.Context;
SourceManager &SM = *Result.SourceManager;

const Expr *LCore = strip(L);
const Expr *RCore = strip(R);
const bool LIsConst = isSimpleConstantExpr(LCore);
const bool RIsConst = isSimpleConstantExpr(RCore);

// Only when exactly one side is constant.
if (LIsConst == RIsConst)
return;
Comment on lines +134 to +135
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this case.


const bool PreferRight = (PreferredSide == "Right");

// If it's already on the preferred side -> nothing to do.
if ((PreferRight && RIsConst && !LIsConst) ||
(!PreferRight && LIsConst && !RIsConst))
return;

// At this point: exactly one side is constant, and it's on the *wrong* side.
// Emit diagnosis (tests expect a warning even when we won't provide a fix).
auto D =
diag(Bin->getOperatorLoc(), "constant operand should be on the %0 side")
<< PreferredSide;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should lowercase this string, because right now, we get messages like:

warning: constant operand should be on the Right side

With "Right" awkwardly capitalized


// Conservative: don't offer fix-its if swapping would move side-effects or if
// we're inside a macro expansion.
const bool LSE = L->HasSideEffects(Ctx);
const bool RSE = R->HasSideEffects(Ctx);
const bool AnyMacro = L->getBeginLoc().isMacroID() ||
R->getBeginLoc().isMacroID() ||
Bin->getOperatorLoc().isMacroID();
Comment on lines +154 to +156
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. Missing tests.

if (LSE || RSE || AnyMacro)
return; // warning-only: no FixIt attached.

// Get token ranges for the two operands.
CharSourceRange LRange = CharSourceRange::getTokenRange(L->getSourceRange());
CharSourceRange RRange = CharSourceRange::getTokenRange(R->getSourceRange());
if (LRange.isInvalid() || RRange.isInvalid())
return;

llvm::StringRef LText = Lexer::getSourceText(LRange, SM, Ctx.getLangOpts());
llvm::StringRef RText = Lexer::getSourceText(RRange, SM, Ctx.getLangOpts());
Comment on lines +166 to +167
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
llvm::StringRef LText = Lexer::getSourceText(LRange, SM, Ctx.getLangOpts());
llvm::StringRef RText = Lexer::getSourceText(RRange, SM, Ctx.getLangOpts());
const StringRef LText = Lexer::getSourceText(LRange, SM, Ctx.getLangOpts());
const StringRef RText = Lexer::getSourceText(RRange, SM, Ctx.getLangOpts());

if (LText.empty() || RText.empty())
return;

// Compute operator replacement (invert for asymmetric operators).
const StringRef OpName = Bin->getOpcodeStr();
const std::string NewOp = invertOperatorText(OpName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const std::string NewOp = invertOperatorText(OpName);
const StringRef NewOp = invertOperatorText(OpName);


// Apply operand swaps as two independent replacements (safer than replacing
// the whole Bin range).
// Replace left operand with right text:
D << FixItHint::CreateReplacement(LRange, RText.str());
// Replace right operand with left text:
D << FixItHint::CreateReplacement(RRange, LText.str());

// If needed, replace the operator token too.
if (NewOp != OpName.str()) {
// Compute an operator token range robustly: operator start and end.
SourceLocation OpStart = Bin->getOperatorLoc();
SourceLocation OpEnd =
Lexer::getLocForEndOfToken(OpStart, 0, SM, Ctx.getLangOpts());
if (OpStart.isValid() && OpEnd.isValid()) {
SourceRange OpRange(OpStart, OpEnd);
CharSourceRange OpTok = CharSourceRange::getTokenRange(OpRange);
Comment on lines +185 to +190
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SourceLocation OpStart = Bin->getOperatorLoc();
SourceLocation OpEnd =
Lexer::getLocForEndOfToken(OpStart, 0, SM, Ctx.getLangOpts());
if (OpStart.isValid() && OpEnd.isValid()) {
SourceRange OpRange(OpStart, OpEnd);
CharSourceRange OpTok = CharSourceRange::getTokenRange(OpRange);
const SourceLocation OpStart = Bin->getOperatorLoc();
const SourceLocation OpEnd =
Lexer::getLocForEndOfToken(OpStart, 0, SM, Ctx.getLangOpts());
if (OpStart.isValid() && OpEnd.isValid()) {
const SourceRange OpRange(OpStart, OpEnd);
const CharSourceRange OpTok = CharSourceRange::getTokenRange(OpRange);

if (!OpTok.isInvalid())
D << FixItHint::CreateReplacement(OpTok, NewOp);
Comment on lines +188 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up above, we unconditionally provide fix-its to swap the operands, but we might not reach this code that provides the fix-it to adjust the operator, resulting in a broken fix-it. Either all the fix-its should be provided or none of them.

}
}
}

} // namespace clang::tidy::readability
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTANTOPERANDORDERCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTANTOPERANDORDERCHECK_H

#include "../ClangTidyCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

#include <string>
#include <vector>

namespace clang::tidy::readability {
/// Finds binary expressions where the constant operand appears on the
/// non-preferred side (configurable) and offers a fix-it that swaps operands
/// (and inverts the operator for asymmetric operators like `<` / `>`).
/// Options
/// - BinaryOperators: comma-separated list of operators to check
/// (default: "==,!=,<,<=,>,>=")
/// - PreferredConstantSide: "Left" or "Right" (default: "Right")
class ConstantOperandOrderCheck : public ClangTidyCheck {
public:
ConstantOperandOrderCheck(StringRef Name, ClangTidyContext *Context);
~ConstantOperandOrderCheck() override;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed.

Suggested change
~ConstantOperandOrderCheck() override;

(The definition in the .cpp should be deleted too)


// Persist options so they show up in config files.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;

void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
// This check is primarily for C/C++, keep it limited to C++ for now.
return LangOpts.CPlusPlus;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the check could be enabled for all languages. Looking at its code, I don't see anything limiting it to C++.

}

private:
// Option keys (used when storing & reading options)
static constexpr llvm::StringLiteral BinaryOperatorsOption =
"BinaryOperators";
static constexpr llvm::StringLiteral PreferredSideOption =
"PreferredConstantSide";

// Runtime values, populated from Options in the constructor (or storeOptions)
std::string PreferredSide; // "Left" or "Right"
std::vector<std::string> Operators; // list of operator names, e.g. "=="
};

} // namespace clang::tidy::readability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTANTOPERANDORDERCHECK_H
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "AvoidUnconditionalPreprocessorIfCheck.h"
#include "BracesAroundStatementsCheck.h"
#include "ConstReturnTypeCheck.h"
#include "ConstantOperandOrderCheck.h"
#include "ContainerContainsCheck.h"
#include "ContainerDataPointerCheck.h"
#include "ContainerSizeEmptyCheck.h"
Expand Down Expand Up @@ -86,6 +87,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-braces-around-statements");
CheckFactories.registerCheck<ConstReturnTypeCheck>(
"readability-const-return-type");
CheckFactories.registerCheck<ConstantOperandOrderCheck>(
"readability-constant-operand-order");
CheckFactories.registerCheck<ContainerContainsCheck>(
"readability-container-contains");
CheckFactories.registerCheck<ContainerDataPointerCheck>(
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ New checks
Finds virtual function overrides with different visibility than the function
in the base class.

- New :doc:`readability-constant-operand-order
<clang-tidy/checks/readability/constant-operand-order>` check.

FIXME: Write a short description.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing one sentence description.


- New :doc:`readability-redundant-parentheses
<clang-tidy/checks/readability/redundant-parentheses>` check.

Expand Down
17 changes: 9 additions & 8 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,13 @@ Clang-Tidy Checks
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase artifact?

:doc:`cert-env33-c <cert/env33-c>`,
:doc:`cert-err33-c <cert/err33-c>`,
:doc:`cert-err52-cpp <cert/err52-cpp>`,
:doc:`cert-err60-cpp <cert/err60-cpp>`,
:doc:`cert-flp30-c <cert/flp30-c>`,
:doc:`cert-mem57-cpp <cert/mem57-cpp>`,
:doc:`cert-msc50-cpp <cert/msc50-cpp>`,
:doc:`cert-msc51-cpp <cert/msc51-cpp>`,
:doc:`cert-oop58-cpp <cert/oop58-cpp>`,
Expand Down Expand Up @@ -373,8 +378,9 @@ Clang-Tidy Checks
:doc:`readability-avoid-nested-conditional-operator <readability/avoid-nested-conditional-operator>`,
:doc:`readability-avoid-return-with-void-value <readability/avoid-return-with-void-value>`, "Yes"
:doc:`readability-avoid-unconditional-preprocessor-if <readability/avoid-unconditional-preprocessor-if>`,
:doc:`readability-braces-around-statements <readability/braces-around-statements>`, "Yes"
:doc:`readability-braces-around-statements <readability/braces-around-statements>`,
:doc:`readability-const-return-type <readability/const-return-type>`, "Yes"
:doc:`readability-constant-operand-order <readability/constant-operand-order>`, "Yes"
:doc:`readability-container-contains <readability/container-contains>`, "Yes"
:doc:`readability-container-data-pointer <readability/container-data-pointer>`, "Yes"
:doc:`readability-container-size-empty <readability/container-size-empty>`, "Yes"
Expand Down Expand Up @@ -442,20 +448,15 @@ Check aliases
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`, :doc:`modernize-avoid-variadic-functions <modernize/avoid-variadic-functions>`,
:doc:`cert-dcl51-cpp <cert/dcl51-cpp>`, :doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
:doc:`cert-dcl54-cpp <cert/dcl54-cpp>`, :doc:`misc-new-delete-overloads <misc/new-delete-overloads>`,
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`, :doc:`bugprone-std-namespace-modification <bugprone/std-namespace-modification>`,
:doc:`cert-dcl59-cpp <cert/dcl59-cpp>`, :doc:`google-build-namespaces <google/build-namespaces>`,
:doc:`cert-env33-c <cert/env33-c>`, :doc:`bugprone-command-processor <bugprone/command-processor>`,
:doc:`cert-err09-cpp <cert/err09-cpp>`, :doc:`misc-throw-by-value-catch-by-reference <misc/throw-by-value-catch-by-reference>`,
:doc:`cert-err34-c <cert/err34-c>`, :doc:`bugprone-unchecked-string-to-number-conversion <bugprone/unchecked-string-to-number-conversion>`,
:doc:`cert-err52-cpp <cert/err52-cpp>`, :doc:`modernize-avoid-setjmp-longjmp <modernize/avoid-setjmp-longjmp>`,
:doc:`cert-err58-cpp <cert/err58-cpp>`, :doc:`bugprone-throwing-static-initialization <bugprone/throwing-static-initialization>`,
:doc:`cert-err61-cpp <cert/err61-cpp>`, :doc:`misc-throw-by-value-catch-by-reference <misc/throw-by-value-catch-by-reference>`,
:doc:`cert-exp42-c <cert/exp42-c>`, :doc:`bugprone-suspicious-memory-comparison <bugprone/suspicious-memory-comparison>`,
:doc:`cert-fio38-c <cert/fio38-c>`, :doc:`misc-non-copyable-objects <misc/non-copyable-objects>`,
:doc:`cert-flp30-c <cert/flp30-c>`, :doc:`bugprone-float-loop-counter <bugprone/float-loop-counter>`,
:doc:`cert-flp37-c <cert/flp37-c>`, :doc:`bugprone-suspicious-memory-comparison <bugprone/suspicious-memory-comparison>`,
:doc:`cert-int09-c <cert/int09-c>`, :doc:`readability-enum-initial-value <readability/enum-initial-value>`, "Yes"
:doc:`cert-mem57-cpp <cert/mem57-cpp>`, :doc:`bugprone-default-operator-new-on-overaligned-type <bugprone/default-operator-new-on-overaligned-type>`,
:doc:`cert-msc24-c <cert/msc24-c>`, :doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
:doc:`cert-msc30-c <cert/msc30-c>`, :doc:`cert-msc50-cpp <cert/msc50-cpp>`,
:doc:`cert-msc32-c <cert/msc32-c>`, :doc:`cert-msc51-cpp <cert/msc51-cpp>`,
Expand Down Expand Up @@ -578,12 +579,12 @@ Check aliases
:doc:`cppcoreguidelines-non-private-member-variables-in-classes <cppcoreguidelines/non-private-member-variables-in-classes>`, :doc:`misc-non-private-member-variables-in-classes <misc/non-private-member-variables-in-classes>`,
:doc:`cppcoreguidelines-use-default-member-init <cppcoreguidelines/use-default-member-init>`, :doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes"
:doc:`fuchsia-header-anon-namespaces <fuchsia/header-anon-namespaces>`, :doc:`google-build-namespaces <google/build-namespaces>`,
:doc:`google-readability-braces-around-statements <google/readability-braces-around-statements>`, :doc:`readability-braces-around-statements <readability/braces-around-statements>`, "Yes"
:doc:`google-readability-braces-around-statements <google/readability-braces-around-statements>`, :doc:`readability-braces-around-statements <readability/braces-around-statements>`,
:doc:`google-readability-function-size <google/readability-function-size>`, :doc:`readability-function-size <readability/function-size>`,
:doc:`google-readability-namespace-comments <google/readability-namespace-comments>`, :doc:`llvm-namespace-comment <llvm/namespace-comment>`,
:doc:`hicpp-avoid-c-arrays <hicpp/avoid-c-arrays>`, :doc:`modernize-avoid-c-arrays <modernize/avoid-c-arrays>`,
:doc:`hicpp-avoid-goto <hicpp/avoid-goto>`, :doc:`cppcoreguidelines-avoid-goto <cppcoreguidelines/avoid-goto>`,
:doc:`hicpp-braces-around-statements <hicpp/braces-around-statements>`, :doc:`readability-braces-around-statements <readability/braces-around-statements>`, "Yes"
:doc:`hicpp-braces-around-statements <hicpp/braces-around-statements>`, :doc:`readability-braces-around-statements <readability/braces-around-statements>`,
:doc:`hicpp-deprecated-headers <hicpp/deprecated-headers>`, :doc:`modernize-deprecated-headers <modernize/deprecated-headers>`, "Yes"
:doc:`hicpp-explicit-conversions <hicpp/explicit-conversions>`, :doc:`google-explicit-constructor <google/explicit-constructor>`, "Yes"
:doc:`hicpp-function-size <hicpp/function-size>`, :doc:`readability-function-size <readability/function-size>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. title:: clang-tidy - readability-constant-operand-order

readability-constant-operand-order
==================================

Warns when a constant appears on the non-preferred side of a supported binary
operator and offers a fix-it note to swap operands (and invert the operator for
``<``, ``>``, ``<=``, ``>=``).

Examples
--------

.. code-block:: c++

// Before
if (nullptr == p) { /* ... */ }
if (0 < x) { /* ... */ }
// After
if (p == nullptr) { /* ... */ }
if (x > 0) { /* ... */ }
Options
-------

.. option:: PreferredConstantSide (string)

Either `Left` or `Right`. Default: `Right`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowercase option values?


.. option:: BinaryOperators (string)

Comma-separated list of operators to check. Default: `==,!=,<,<=,>,>=`.
Loading
Loading