[clang-tidy] Add redundant qualified alias check (#180404)

Introduce `readability-redundant-qualified-alias` to flag identity type
aliases that repeat a qualified name and suggest using-declarations when
safe. The check is conservative: it skips macros, elaborated keywords,
dependent types, and templates. `OnlyNamespaceScope` controls whether
local/class scopes are included (default `false`).

Depends on: #183940 #183941
This commit is contained in:
Daniil Dudkin 2026-03-15 01:18:25 +03:00 committed by GitHub
parent 629edaf678
commit 55db533b74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 504 additions and 0 deletions

View File

@ -47,6 +47,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
RedundantMemberInitCheck.cpp
RedundantParenthesesCheck.cpp
RedundantPreprocessorCheck.cpp
RedundantQualifiedAliasCheck.cpp
RedundantSmartptrGetCheck.cpp
RedundantStringCStrCheck.cpp
RedundantStringInitCheck.cpp

View File

@ -49,6 +49,7 @@
#include "RedundantMemberInitCheck.h"
#include "RedundantParenthesesCheck.h"
#include "RedundantPreprocessorCheck.h"
#include "RedundantQualifiedAliasCheck.h"
#include "RedundantSmartptrGetCheck.h"
#include "RedundantStringCStrCheck.h"
#include "RedundantStringInitCheck.h"
@ -148,6 +149,8 @@ public:
"readability-redundant-parentheses");
CheckFactories.registerCheck<RedundantPreprocessorCheck>(
"readability-redundant-preprocessor");
CheckFactories.registerCheck<RedundantQualifiedAliasCheck>(
"readability-redundant-qualified-alias");
CheckFactories.registerCheck<RedundantTypenameCheck>(
"readability-redundant-typename");
CheckFactories.registerCheck<ReferenceToConstructedTemporaryCheck>(

View File

@ -0,0 +1,220 @@
//===----------------------------------------------------------------------===//
//
// 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 "RedundantQualifiedAliasCheck.h"
#include "../utils/LexerUtils.h"
#include <cassert>
#include <optional>
using namespace clang::ast_matchers;
namespace clang::tidy::readability {
namespace {
struct TypeLocInfo {
TypeLoc Loc;
bool HasQualifier = false;
};
} // namespace
static bool hasMacroInRange(SourceRange Range, const SourceManager &SM,
const LangOptions &LangOpts) {
if (Range.isInvalid())
return true;
return utils::lexer::rangeContainsExpansionsOrDirectives(Range, SM, LangOpts);
}
static std::optional<TypeLocInfo> getTypeLocInfo(TypeLoc TL) {
if (TL.isNull())
return std::nullopt;
const auto MakeTypeLocInfo = [](auto TypeTL) {
const bool HasQualifier =
static_cast<bool>(TypeTL.getQualifierLoc().getNestedNameSpecifier());
return TypeLocInfo{TypeTL, HasQualifier};
};
if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>())
return MakeTypeLocInfo(TypedefTL);
if (const auto TagTL = TL.getAs<TagTypeLoc>())
return MakeTypeLocInfo(TagTL);
return std::nullopt;
}
static const NamedDecl *getNamedDeclFromTypeLoc(TypeLoc TL) {
if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>())
return TypedefTL.getDecl();
if (const auto TagTL = TL.getAs<TagTypeLoc>())
return TagTL.getDecl();
return nullptr;
}
static bool hasSameUnqualifiedName(const NamedDecl *LHS, const NamedDecl *RHS) {
return LHS->getName() == RHS->getName();
}
static bool isNamespaceLikeDeclContext(const DeclContext *DC) {
return isa<TranslationUnitDecl, NamespaceDecl>(DC);
}
static bool canUseUsingDeclarationForTarget(const TypeAliasDecl *Alias,
const NamedDecl *Target) {
const DeclContext *AliasContext = Alias->getDeclContext()->getRedeclContext();
const DeclContext *TargetContext =
Target->getDeclContext()->getRedeclContext();
const auto *AliasRecord = dyn_cast<CXXRecordDecl>(AliasContext);
if (!AliasRecord)
return isNamespaceLikeDeclContext(TargetContext);
const auto *TargetRecord = dyn_cast<CXXRecordDecl>(TargetContext);
return TargetRecord && AliasRecord->isDerivedFrom(TargetRecord);
}
static bool hasTrailingSyntaxAfterRhsType(TypeLoc TL, const SourceManager &SM,
const LangOptions &LangOpts) {
const SourceLocation TypeEndLoc = TL.getEndLoc();
if (TypeEndLoc.isInvalid() || TypeEndLoc.isMacroID())
return true;
const std::optional<Token> NextToken =
utils::lexer::findNextTokenSkippingComments(TypeEndLoc, SM, LangOpts);
return !NextToken || NextToken->isNot(tok::semi);
}
namespace {
AST_MATCHER(TypeAliasDecl, isAliasTemplate) {
return Node.getDescribedAliasTemplate() != nullptr;
}
AST_MATCHER(NamedDecl, isInMacro) { return Node.getLocation().isMacroID(); }
AST_MATCHER(TypeAliasDecl, hasAliasAttributes) {
if (Node.hasAttrs())
return true;
const TypeSourceInfo *TSI = Node.getTypeSourceInfo();
if (!TSI)
return false;
for (TypeLoc CurTL = TSI->getTypeLoc(); !CurTL.isNull();
CurTL = CurTL.getNextTypeLoc())
if (CurTL.getAs<AttributedTypeLoc>())
return true;
return false;
}
AST_MATCHER(TypeLoc, isNonDependentTypeLoc) {
return !Node.getType().isNull() && !Node.getType()->isDependentType();
}
AST_MATCHER(TypeLoc, isNonElaboratedTypeLoc) {
const auto IsNonElaboratedTypeLoc = [](auto TL) {
return !TL.isNull() && !TL.getElaboratedKeywordLoc().isValid();
};
return IsNonElaboratedTypeLoc(Node.getAs<TypedefTypeLoc>()) ||
IsNonElaboratedTypeLoc(Node.getAs<TagTypeLoc>());
}
AST_MATCHER(TypeLoc, isMacroFreeTypeLoc) {
const ASTContext &Context = Finder->getASTContext();
return !hasMacroInRange(Node.getSourceRange(), Context.getSourceManager(),
Context.getLangOpts());
}
AST_MATCHER(TypeLoc, hasNoTrailingSyntaxAfterTypeLoc) {
const ASTContext &Context = Finder->getASTContext();
return !hasTrailingSyntaxAfterRhsType(Node, Context.getSourceManager(),
Context.getLangOpts());
}
AST_MATCHER(TypeAliasDecl, hasUsingDeclarationEquivalentTarget) {
const TypeSourceInfo *TSI = Node.getTypeSourceInfo();
if (!TSI)
return false;
const std::optional<TypeLocInfo> TypeInfo = getTypeLocInfo(TSI->getTypeLoc());
if (!TypeInfo || !TypeInfo->HasQualifier)
return false;
const NamedDecl *Target = getNamedDeclFromTypeLoc(TypeInfo->Loc);
return Target && hasSameUnqualifiedName(&Node, Target) &&
canUseUsingDeclarationForTarget(&Node, Target);
}
} // namespace
RedundantQualifiedAliasCheck::RedundantQualifiedAliasCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
OnlyNamespaceScope(Options.get("OnlyNamespaceScope", false)) {}
void RedundantQualifiedAliasCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "OnlyNamespaceScope", OnlyNamespaceScope);
}
void RedundantQualifiedAliasCheck::registerMatchers(MatchFinder *Finder) {
const auto ControlFlowInitStatementMatcher = stmt(
anyOf(mapAnyOf(ifStmt, switchStmt, cxxForRangeStmt)
.with(hasInitStatement(stmt(equalsBoundNode("initDeclStmt")))),
forStmt(hasLoopInit(stmt(equalsBoundNode("initDeclStmt"))))));
const auto AliasPreconditions =
allOf(unless(isInMacro()), unless(isAliasTemplate()),
unless(hasAliasAttributes()));
const auto InControlFlowInit =
allOf(hasParent(declStmt().bind("initDeclStmt")),
hasAncestor(ControlFlowInitStatementMatcher));
const auto RewriteableTypeLoc =
typeLoc(allOf(isNonDependentTypeLoc(), isNonElaboratedTypeLoc(),
isMacroFreeTypeLoc(), hasNoTrailingSyntaxAfterTypeLoc()))
.bind("loc");
const auto RedundantQualifiedAliasMatcher = typeAliasDecl(
AliasPreconditions, unless(InControlFlowInit),
hasUsingDeclarationEquivalentTarget(), hasTypeLoc(RewriteableTypeLoc));
if (OnlyNamespaceScope) {
Finder->addMatcher(typeAliasDecl(RedundantQualifiedAliasMatcher,
hasDeclContext(anyOf(translationUnitDecl(),
namespaceDecl())))
.bind("alias"),
this);
return;
}
Finder->addMatcher(RedundantQualifiedAliasMatcher.bind("alias"), this);
}
void RedundantQualifiedAliasCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Alias = Result.Nodes.getNodeAs<TypeAliasDecl>("alias");
assert(Alias && "matcher must bind alias");
const auto *WrittenTLNode = Result.Nodes.getNodeAs<TypeLoc>("loc");
assert(WrittenTLNode && "matcher must bind loc");
const TypeLoc WrittenTL = *WrittenTLNode;
const SourceManager &SM = *Result.SourceManager;
const LangOptions &LangOpts = getLangOpts();
const SourceLocation AliasLoc = Alias->getLocation();
const SourceLocation RhsBeginLoc = WrittenTL.getBeginLoc();
const CharSourceRange EqualRange = utils::lexer::findTokenTextInRange(
CharSourceRange::getCharRange(AliasLoc, RhsBeginLoc), SM, LangOpts,
[](const Token &Tok) { return Tok.is(tok::equal); });
if (EqualRange.isInvalid())
return;
auto Diag = diag(Alias->getLocation(),
"type alias is redundant; use a using-declaration instead");
Diag << FixItHint::CreateRemoval(Alias->getLocation())
<< FixItHint::CreateRemoval(EqualRange.getBegin());
}
} // namespace clang::tidy::readability

View File

@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// 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_REDUNDANTQUALIFIEDALIASCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H
#include "../ClangTidyCheck.h"
namespace clang::tidy::readability {
/// Finds identity type aliases to qualified names that can be expressed as
/// using-declarations.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-qualified-alias.html
class RedundantQualifiedAliasCheck : public ClangTidyCheck {
public:
RedundantQualifiedAliasCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus11;
}
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const bool OnlyNamespaceScope;
};
} // namespace clang::tidy::readability
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H

View File

@ -154,6 +154,12 @@ New checks
Suggests insertion of ``std::move(...)`` to turn copy assignment operator
calls into move assignment ones, when deemed valid and profitable.
- New :doc:`readability-redundant-qualified-alias
<clang-tidy/checks/readability/redundant-qualified-alias>` check.
Finds redundant identity type aliases that re-expose a qualified name and can
be replaced with a ``using`` declaration.
- New :doc:`readability-trailing-comma
<clang-tidy/checks/readability/trailing-comma>` check.

View File

@ -417,6 +417,7 @@ Clang-Tidy Checks
:doc:`readability-redundant-member-init <readability/redundant-member-init>`, "Yes"
:doc:`readability-redundant-parentheses <readability/redundant-parentheses>`, "Yes"
:doc:`readability-redundant-preprocessor <readability/redundant-preprocessor>`,
:doc:`readability-redundant-qualified-alias <readability/redundant-qualified-alias>`, "Yes"
:doc:`readability-redundant-smartptr-get <readability/redundant-smartptr-get>`, "Yes"
:doc:`readability-redundant-string-cstr <readability/redundant-string-cstr>`, "Yes"
:doc:`readability-redundant-string-init <readability/redundant-string-init>`, "Yes"

View File

@ -0,0 +1,30 @@
.. title:: clang-tidy - readability-redundant-qualified-alias
readability-redundant-qualified-alias
=====================================
Finds redundant identity type aliases that re-expose a qualified name and can
be replaced with a ``using`` declaration.
.. code-block:: c++
using seconds = std::chrono::seconds;
// becomes
using std::chrono::seconds;
The check is conservative and only warns when the alias name exactly matches
the unqualified name of a non-dependent, non-specialized named type written
with a qualifier. It skips alias templates, dependent forms, elaborated
keywords (``class``, ``struct``, ``enum``, ``typename``), and cases involving
macros.
Options
-------
.. option:: OnlyNamespaceScope
When `true`, only consider aliases declared in a namespace or the
translation unit. When `false`, also consider aliases declared inside
classes, functions, and lambdas. Default is `false`.

View File

@ -0,0 +1,203 @@
// RUN: %check_clang_tidy -std=c++11-or-later %s readability-redundant-qualified-alias %t
// RUN: %check_clang_tidy -check-suffix=NS -std=c++11-or-later %s readability-redundant-qualified-alias %t -- \
// RUN: -config='{CheckOptions: { readability-redundant-qualified-alias.OnlyNamespaceScope: true }}'
// RUN: %check_clang_tidy -check-suffixes=,CXX23 -std=c++23-or-later %s readability-redundant-qualified-alias %t
// RUN: %check_clang_tidy -check-suffixes=NS,NS-CXX23 -std=c++23-or-later %s readability-redundant-qualified-alias %t -- \
// RUN: -config='{CheckOptions: { readability-redundant-qualified-alias.OnlyNamespaceScope: true }}'
namespace n1 {
struct Foo {};
struct Bar {};
struct Attr {};
enum PlainEnum { V0 };
enum class ScopedEnum { V1 };
struct Commented {};
struct AfterType {};
struct Elab {};
struct MacroEq {};
struct MacroType {};
struct PtrType {};
struct LocalType {};
} // namespace n1
namespace n2 {
namespace n3 {
struct Deep {};
} // namespace n3
} // namespace n2
namespace td {
typedef n1::Foo TypedefFoo;
} // namespace td
struct GlobalType {};
struct Outer {
struct Inner {};
};
using Foo = n1::Foo;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias]
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias]
// CHECK-FIXES: using n1::Foo;
using Bar = ::n1::Bar;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using ::n1::Bar;
using Attr = n1::Attr __attribute__((aligned(8)));
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
namespace alias_attr {
using Foo [[deprecated("alias attr")]] = n1::Foo;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
} // namespace alias_attr
using Deep = n2::n3::Deep;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using n2::n3::Deep;
using TypedefFoo = td::TypedefFoo;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using td::TypedefFoo;
using GlobalType = ::GlobalType;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using ::GlobalType;
using PlainEnum = n1::PlainEnum;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using n1::PlainEnum;
using ScopedEnum = n1::ScopedEnum;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using n1::ScopedEnum;
using Inner = Outer::Inner;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
using Builtin = int;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
using PtrType = n1::PtrType *;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
namespace templ {
template <typename T>
struct Vec {};
} // namespace templ
using Vec = templ::Vec<int>;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
namespace templ_alias {
template <typename T>
using Foo = n1::Foo;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
} // namespace templ_alias
template <typename T>
struct Dependent {
using X = typename T::X;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
};
using Elab = class n1::Elab;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
using Commented /*comment*/ = n1::Commented;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using{{[ ]+}}/*comment*/{{[ ]+}}n1::Commented;
using AfterType = n1::AfterType /*rhs-comment*/;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using n1::AfterType /*rhs-comment*/;
#define DECL_END ;
using MacroDeclEnd = n1::MacroType DECL_END
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
#define ALIAS MacroType
using ALIAS = n1::MacroType;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
#define RHS n1::MacroType
using MacroType = RHS;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
#define EQ =
using MacroEq EQ n1::MacroEq;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
struct Base {
using T = n1::Foo;
};
struct Derived : Base {
using T = Base::T;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using Base::T;
};
struct ClassScopeNamespaceAlias {
using Foo = n1::Foo;
// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
};
void local_scope() {
using LocalType = n1::LocalType;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-FIXES: using n1::LocalType;
}
#if __cplusplus >= 202302L
void cxx23_init_statement_scope(bool Cond) {
if (using Foo = n1::Foo; Cond) {
}
// CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
switch (using Bar = ::n1::Bar; 0) {
default:
break;
}
// CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
for (using Deep = n2::n3::Deep; Cond;) {
Cond = false;
}
// CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
int Values[] = {0};
for (using GlobalType = ::GlobalType; int V : Values) {
(void)V;
}
// CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
// CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead
}
#endif // __cplusplus >= 202302L