6953 Commits

Author SHA1 Message Date
Nathan Ridge
fa0498fdae
[clang][HeuristicResolver] Apply default argument heuristic in resolveDeclRefExpr as well (#132576)
This is a follow-up to https://github.com/llvm/llvm-project/pull/131074.

After moving the default argument heuristic to `simplifyType` in that
patch, the heuristic no longer applied to the 
`DependentScopeDeclRefExpr` case, because that wasn't using
`simplifyType`.

This patch fixes that, with an added testcase.
2025-04-11 02:35:04 -04:00
Matheus Izvekov
3954d258a5
[clang] Improved canonicalization for template specialization types (#135119)
This changes the TemplateArgument representation to hold a flag
indicating whether a template argument of expression type is supposed to
be canonical or not.

This gets one step closer to solving
https://github.com/llvm/llvm-project/issues/92292

This still doesn't try to unique as-written TSTs. While this would
increase the amount of memory savings and make code dealing with the AST
more well-behaved, profiling template argument lists is still too
expensive for this to be worthwhile, at least for now. Without this
uniquing, this patch stands neutral in terms of performance impact.

This also fixes the context creation of TSTs, so that they don't in some
cases get incorrectly flagged as sugar over their own canonical form.
This is captured in the test expectation change of some AST dumps.

This fixes some places which were unnecessarily canonicalizing these
TSTs.
2025-04-10 14:23:02 -03:00
sstwcw
f7617f7f90
[clang-format] Recognize TableGen paste operator on separate line (#133722)
Formatting this piece of code made the program crash.

```
class TypedVecListRegOperand<RegisterClass Reg, int lanes, string eltsize>
    : RegisterOperand<Reg, "printTypedVectorList<" # lanes # ", '"
                                                   # eltsize # "'>">;
```

The line starting with the `#` was treated as a separate preprocessor
directive line. Then the code dereferenced a null pointer when it tried
to continue parsing the first line that did not end in a semicolon.

Now the 2 problems are fixed.
2025-04-10 12:52:02 +00:00
sstwcw
ed85822027
[clang-format] Handle C++ keywords in other languages better (#132941)
There is some code to make sure that C++ keywords that are identifiers
in the other languages are not treated as keywords.  Right now, the kind
is set to identifier, and the identifier info is cleared.  The latter is
probably so that the code for identifying C++ structures does not
recognize those structures by mistake when formatting a language that
does not have those structures.  But we did not find an instance where
the language can have the sequence of tokens, the code tries to parse
the structure as if it is C++ using the identifier info instead of the
token kind, but without checking for the language setting.  However,
there are places where the code checks whether the identifier info field
is null or not.  They are places where an identifier and a keyword are
treated the same way.  For example, the name of a function in
JavaScript.  This patch removes the lines that clear the identifier
info.  This way, a C++ keyword gets treated in the same way as an
identifier in those places.

JavaScript

New

```JavaScript
async function
union(
    myparamnameiswaytooloooong) {
}
```

Old

```JavaScript
async function
    union(
        myparamnameiswaytooloooong) {
}
```

Java

New

```Java
enum union { ABC, CDE }
```

Old

```Java
enum
union { ABC, CDE }
```

This reverts commit 97dcbdef6089175c45e14fcbcf5c88b10233a79a.
2025-04-10 12:51:10 +00:00
Timm Baeder
02f923f8e4
[clang][bytecode] Classify function pointers as PT_Ptr (#135026)
The Pointer class already has the capability to be a function pointer,
but we still classifed function pointers as PT_FnPtr/FunctionPointer.
This means when converting from a Pointer to a FunctionPointer, we lost
the information of what the original Pointer pointed to.
2025-04-10 06:40:54 +02:00
Owen Pan
f344838389
[clang-format] Keep the space between not and a unary operator (#135035)
Also keep the space between `not` and `::`.

Based on the
[documentation](https://releases.llvm.org/20.1.0/tools/clang/docs/ClangFormatStyleOptions.html#spaceafterlogicalnot),
it can be argued that SpaceAfterLogicalNot doesn't cover the alternative
operator `not`.

Closes #125465
2025-04-09 17:52:12 -07:00
Owen Pan
aaaeb86ace
[clang-format] Merge inline short functions for BS_Whitesmiths (#134473)
Fix #126747
2025-04-05 17:03:55 -07:00
Owen Pan
d71ee7d230
[clang-format] Set C11 instead of C17 for LK_C (#134472)
Fix #134453
2025-04-05 13:35:45 -07:00
Finn Plummer
428fc2c887
[NFC][HLSL][RootSignature] Make the Lexer adhere to naming conventions (#134136)
- when developing the RootSignatureLexer library, we are creating new
files so we should set the standard to adhere to the coding conventions
for function naming
- this was missed in the initial review but caught in the review of the
parser pr
[here](https://github.com/llvm/llvm-project/pull/133302#discussion_r2017632092)

Co-authored-by: Finn Plummer <finnplummer@microsoft.com>
2025-04-04 13:43:45 -07:00
Jan Svoboda
ea0869ccb9 [clang][parse] Fix build of ParseHLSLRootSignatureTest.cpp
Fallout from PR #133467.
2025-04-04 10:20:44 -07:00
Jan Svoboda
1688c3062a
[clang] Do not share ownership of PreprocessorOptions (#133467)
This PR makes it so that `CompilerInvocation` is the sole owner of the
`PreprocessorOptions` instance.
2025-04-04 10:11:14 -07:00
Matheus Izvekov
cfee056b4e
[clang] NFC: introduce UnsignedOrNone as a replacement for std::optional<unsigned> (#134142)
This introduces a new class 'UnsignedOrNone', which models a lite
version of `std::optional<unsigned>`, but has the same size as
'unsigned'.

This replaces most uses of `std::optional<unsigned>`, and similar
schemes utilizing 'int' and '-1' as sentinel.

Besides the smaller size advantage, this is simpler to serialize, as its
internal representation is a single unsigned int as well.
2025-04-03 14:27:18 -03:00
Ilya Biryukov
722346c7bc
[Tooling] Handle AttributedType in getFullyQualifiedType (#134228)
Before this change the code used to add extra qualifiers, e.g.
`std::unique_ptr<int> _Nonnull` became `::std::std::unique_ptr<int>
_Nonnull`
when adding a global namespace qualifier was requested.
2025-04-03 14:14:34 +02:00
Reid Kleckner
e3c0565b74
Reapply "[cmake] Refactor clang unittest cmake" (#134195)
This reapplies 5ffd9bdb50b57 (#133545) with fixes.

The BUILD_SHARED_LIBS=ON build was fixed by adding missing LLVM
dependencies to the InterpTests binary in
unittests/AST/ByteCode/CMakeLists.txt .
2025-04-02 21:07:30 -07:00
Owen Pan
4fe0d74275
[clang-format] Fix a bug in annotating braces (#134039)
Fix #133873
2025-04-02 20:08:56 -07:00
dpalermo
03a791f703
Revert "[cmake] Refactor clang unittest cmake" (#134022)
Reverts llvm/llvm-project#133545

This change is breaking several buildbots as well as developer's builds.
Reverting to allow people to make progress.
2025-04-01 22:19:27 -05:00
Owen Pan
97dcbdef60 Revert "[clang-format] Handle C++ keywords in other languages better (#132941)"
This reverts commit ab7cee8a0ecf29fdb47c64c8d431a694d63390d2 which had
formatting errors.
2025-04-01 18:59:12 -07:00
Matheus Izvekov
ad1ca5f4a2
[clang] Concepts: support pack expansions for type constraints (#132626)
This reverts an earlier attempt
(adb0d8ddceb143749c519d14b8b31b481071da77 and
50e5411e4247421fd606f0a206682fcdf0303ae3) to support these expansions,
which was limited to type arguments and which subverted the purpose
of SubstTemplateTypeParmType.

This propagates the ArgumentPackSubstitutionIndex along with the
AssociatedConstraint, so that the pack expansion works, without
needing any new transforms or otherwise any changes to the template
instantiation process.

This keeps the tests from the reverted commits, and adds a few more
showing the new solution also works for NTTPs.

Fixes https://github.com/llvm/llvm-project/issues/131798
2025-04-01 21:11:56 -03:00
Finn Plummer
676755561d
Reland "[HLSL][RootSignature] Implement parsing of a DescriptorTable with empty clauses" (#133958)
This pr relands https://github.com/llvm/llvm-project/pull/133302.

It resolves two issues:
- Linking error during build,
[here](https://github.com/llvm/llvm-project/pull/133302#issuecomment-2767259848).
There was a missing dependency for `clangLex` for the
`ParseHLSLRootSignatureTest.cpp` unit testing. This library was added to
the dependencies to resolve the error. It wasn't caught previously as
the library was transitively linked in most build environments
- Warning of unused declaration,
[here](https://github.com/llvm/llvm-project/pull/133302#issuecomment-2767091368).
There was a usability line in `LexHLSLRootSignature.h` of the form
`using TokenKind = enum RootSignatureToken::Kind` which causes this
error. The declaration is removed from the header file to be used
locally in the `.cpp` files that use it.
Notably, the original pr would also exposed `clang::hlsl::TokenKind` to
everywhere it was included, which had a name clash with
`tok::TokenKind`. This is another motivation to change to the proposed
resolution.

---------

Co-authored-by: Finn Plummer <finnplummer@microsoft.com>
2025-04-01 14:58:30 -07:00
Reid Kleckner
5ffd9bdb50
[cmake] Refactor clang unittest cmake (#133545)
Pass all the dependencies into add_clang_unittest. This is consistent
with how it is done for LLDB. I borrowed the same named argument list
structure from add_lldb_unittest. This is a necessary step towards
consolidating unit tests into fewer binaries, but seems like a good
refactoring in its own right.
2025-04-01 14:12:44 -07:00
Owen Pan
d3be29642f
[clang-format] Correctly annotate pointer/reference in _Generic (#133673)
Fix #133663
2025-03-31 23:16:41 -07:00
Finn Plummer
5e2860a8d3
Revert "[HLSL][RootSignature] Implement parsing of a DescriptorTable with empty clauses" (#133790)
Reverts llvm/llvm-project#133302

Reverting to inspect build failures that were introduced from use of the
`clang::Preprocessor` in unit testing, as well as, the warning about an
unused declaration. See linked issue for failures.
2025-03-31 13:38:09 -07:00
Finn Plummer
e4b9486056
[HLSL][RootSignature] Implement parsing of a DescriptorTable with empty clauses (#133302)
- defines the Parser class and an initial set of helper methods to
support consuming tokens. functionality is demonstrated through a simple
empty descriptor table test case
- defines an initial in-memory representation of a DescriptorTable
- implements a test harness that will be used to validate the correct
diagnostics are generated. it will construct a dummy pre-processor with
diagnostics consumer to do so

Implements the first part of
https://github.com/llvm/llvm-project/issues/126569
2025-03-31 10:26:51 -07:00
sstwcw
ab7cee8a0e [clang-format] Handle C++ keywords in other languages better (#132941)
There is some code to make sure that C++ keywords that are identifiers
in the other languages are not treated as keywords.  Right now, the kind
is set to identifier, and the identifier info is cleared.  The latter is
probably so that the code for identifying C++ structures does not
recognize those structures by mistake when formatting a language that
does not have those structures.  But we did not find an instance where
the language can have the sequence of tokens, the code tries to parse
the structure as if it is C++ using the identifier info instead of the
token kind, but without checking for the language setting.  However,
there are places where the code checks whether the identifier info field
is null or not.  They are places where an identifier and a keyword are
treated the same way.  For example, the name of a function in
JavaScript.  This patch removes the lines that clear the identifier
info.  This way, a C++ keyword gets treated in the same way as an
identifier in those places.

JavaScript

New

```JavaScript
async function
union(
    myparamnameiswaytooloooong) {
}
```

Old

```JavaScript
async function
    union(
        myparamnameiswaytooloooong) {
}
```

Java

New

```Java
enum union { ABC, CDE }
```

Old

```Java
enum
union { ABC, CDE }
```
2025-03-31 13:54:49 +00:00
sstwcw
cb54026d92
[clang-format] Recognize wait fork in Verilog (#132042)
before

```Verilog
wait fork
  ;
  wait fork
    ;
    wait fork
      ;
```

after

```Verilog
wait fork;
wait fork;
wait fork;
```

The `wait fork` statement should not start a block. Previously the
formatter treated the `fork` part as the start of a new block. Now the
problem is fixed.
2025-03-31 13:53:23 +00:00
Owen Pan
e5fcbfa2aa
[clang-format] Add an option for editing enum trailing commas (#133576)
Also refactor the code that removes/replaces a token.
2025-03-30 16:02:49 -07:00
Qinkun Bao
0cd82327ff
Fix some typos (NFC) (#133558) 2025-03-29 20:54:15 +01:00
Florian Mayer
842f25a5fb
[NFC] [dataflow] generalize smart pointer caching (#133350)
This allows us to use it for classes that use other names than get /
value.
2025-03-28 17:51:30 -07:00
Finn Plummer
83c4cb36aa
[HLSL][RootSignature] Make Root Signature lexer keywords case-insensitive (#132967)
From the corrections to the Root Signature specification here:
https://github.com/llvm/wg-hlsl/issues/192. It was denoted that keywords
are also case-insensitive in DXC.

This pr adjusts the lexer to adhere to the updated spec.

We also have a NFC to add a missing license to a file while in the area.

---------

Co-authored-by: Finn Plummer <finnplummer@microsoft.com>
2025-03-28 14:27:24 -07:00
Owen Pan
05fb8408de
[clang-format] Allow Language: Cpp for C files (#133033)
Fix #132832
2025-03-27 01:00:02 -07:00
Owen Pan
b8a0558dea
[clang-format] Fix a regression on annotating template angles (#132885)
Annotate the angles in `A<B != A>B` as template opener/closer as it's
unlikely that they are less/greater-than operators in this context.

Fix #132248
2025-03-27 00:57:17 -07:00
Discookie
a9a8338797
Reland [clang][dataflow] Fix unsupported types always being equal (#131575)
Relands #129502.

Previously when the framework encountered unsupported values (such as
enum classes), they were always treated as equal when comparing with
`==`, regardless of their actual values being different.
Now the two sides are only equal if there's a Value assigned to them.

Added handling for the special case of `nullptr == nullptr`, to preserve
the behavior of untyped `nullptr` having no value.
2025-03-26 09:34:37 +00:00
Jan Svoboda
7a370748c0
[clang][lex] Store non-owning options ref in HeaderSearch (#132780)
This makes it so that `CompilerInvocation` can be the only entity that
manages ownership of `HeaderSearchOptions`, making it possible to
implement copy-on-write semantics.
2025-03-25 12:14:06 -07:00
Owen Pan
2497945a95
[clang-format] Correctly annotate requires clause in && requires( (#132882)
Fix #132334
2025-03-25 08:19:43 -07:00
Owen Pan
da7f1564a8
[clang-format] Don't wrap before attributes in parameter lists (#132519)
Fix #132240
2025-03-24 19:18:13 -07:00
Carlos Galvez
0fb4ef40b1
Revert "[clang-tidy] Avoid processing declarations in system headers … (#132743)
…(#128150)"

This was too aggressive, and leads to problems for downstream users:
https://github.com/llvm/llvm-project/pull/128150#issuecomment-2739803409

Let's revert and reland it once we have addressed the problems.

This reverts commit e4a8969e56572371201863594b3a549de2e23f32.

Co-authored-by: Carlos Gálvez <carlos.galvez@zenseact.com>
2025-03-24 20:47:57 +01:00
Kazu Hirata
c6c394634c
[clang] Use *Set::insert_range (NFC) (#132507)
DenseSet, SmallPtrSet, SmallSet, SetVector, and StringSet recently
gained C++23-style insert_range.  This patch replaces:

  Dest.insert(Src.begin(), Src.end());

with:

  Dest.insert_range(Src);

This patch does not touch custom begin like succ_begin for now.
2025-03-22 08:06:38 -07:00
Nathan Ridge
44573bc152
[clang][HeuristicResolver] Default argument heuristic for template parameters (#132465)
Fixes https://github.com/clangd/clangd/discussions/1056
2025-03-21 19:15:18 -04:00
Florian Mayer
1cbcaa4580
[clang][dataflow] Add matcher for pointer-like types to be cached (#132314)
This is used e.g. for iterators.
2025-03-21 13:27:11 -04:00
Matheus Izvekov
14f7bd63b9
Reland: [clang] preserve class type sugar when taking pointer to member (#132401)
Original PR: #130537
Originally reverted due to revert of dependent commit. Relanding with no
changes.

This changes the MemberPointerType representation to use a
NestedNameSpecifier instead of a Type to represent the base class.

Since the qualifiers are always parsed as nested names, there was an
impedance mismatch when converting these back and forth into types, and
this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences
which a QualType can represent cannot be expressed syntatically, and
they represent the use case more exactly, being either dependent or
referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for
a {up/downcast}cast conversion of the base class, although for now the
underlying type is canonical, as preserving the sugar up to that point
requires further work.

As usual, includes a few drive-by fixes in order to make use of the
improvements.
2025-03-21 13:20:52 -03:00
Matheus Izvekov
5151e6d7fe
Revert "Reland: [clang] preserve class type sugar when taking pointer to member" (#132280)
Reverts llvm/llvm-project#132234

Needs to be reverted due to dependency.

This blocks reverting another PR, see here:
https://github.com/llvm/llvm-project/pull/131965#issuecomment-2741619498
2025-03-20 17:52:48 -03:00
Matheus Izvekov
578f38cd08
Reland: [clang] preserve class type sugar when taking pointer to member (#132234)
Original PR: #130537
Reland after updating lldb too.

This changes the MemberPointerType representation to use a
NestedNameSpecifier instead of a Type to represent the base class.

Since the qualifiers are always parsed as nested names, there was an
impedance mismatch when converting these back and forth into types, and
this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences
which a QualType can represent cannot be expressed syntatically, and
they represent the use case more exactly, being either dependent or
referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for
a {up/downcast}cast conversion of the base class, although for now the
underlying type is canonical, as preserving the sugar up to that point
requires further work.

As usual, includes a few drive-by fixes in order to make use of the
improvements.
2025-03-20 15:33:54 -03:00
Matheus Izvekov
6cd62ad08c
Revert "[clang] improve class type sugar preservation in pointers to members" (#132215)
Reverts llvm/llvm-project#130537

This missed updating lldb, which we didn't notice due to lack of
pre-commit CI.
2025-03-20 11:09:25 -03:00
Matheus Izvekov
9b1f905b48
[clang] improve class type sugar preservation in pointers to members (#130537)
This changes the MemberPointerType representation to use a
NestedNameSpecifier instead of a Type to represent the class.

Since the qualifiers are always parsed as nested names, there was an
impedance mismatch when converting these back and forth into types, and
this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences
which a QualType can represent cannot be expressed syntactically, and it
also represents the use case more exactly, being either dependent or
referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for
a {up/downcast}cast conversion of the base class, although for now the
underlying type is canonical, as preserving the sugar up to that point
requires further work.

As usual, includes a few drive-by fixes in order to make use of the
improvements, and removing some duplications, for example
CheckBaseClassAccess is deduplicated from across SemaAccess and
SemaCast.
2025-03-20 10:30:24 -03:00
Donát Nagy
ea107d5c63
[NFC][analyzer] Use CheckerBase::getName in checker option handling (#131612)
The virtual method `ProgramPointTag::getTagDescription` had two very
distinct use cases:
- It is printed in the DOT graph visualization of the exploded graph
(that is, a debug printout).
- The checker option handling code used it to query the name of a
checker, which relied on the coincidence that in `CheckerBase` this
method is defined to be equivalent with `getName()`.

This commit switches to using `getName` in the second use case, because
this way we will be able to properly support checkers that have multiple
(separately named) parts.

The method `reportInvalidCheckerOptionName` is extended with an
additional overload that allows specifying the `CheckerPartIdx`. The
methods `getChecker*Option` could be extended analogously in the future,
but they are just convenience wrappers around the variants that directly
take `StringRef CheckerName`, so I'll only do this extension if it's
needed.
2025-03-18 16:11:43 +01:00
Jan Voung
6f659b0060
[clang][dataflow] For bugprone-unchecked-optional-access report range (#131055)
Report the range in diagnostics, in addition to the location
in case the range helps disambiguate a little in chained `->`
expressions.
```
b->a->f->x = 1;
^~~~~~~
```
instead of just:
```
b->a->f->x = 1;
^
```
As a followup we should probably also report the location/range
of an `->` if that operator is used. Like:
```
b->a->f->x = 1;
       ^~
```
2025-03-17 16:04:15 -04:00
Owen Pan
91328dbae9
[clang-format] Correctly annotate user-defined conversion functions (#131434)
Also fix/delete existing invalid/redundant test cases.

Fix #130894
2025-03-16 16:11:39 -07:00
Jan Svoboda
c84d8e8f1c
[clang][modules] Introduce new ModuleCache interface (#131193)
This PR adds new `ModuleCache` interface to Clang's implicitly-built
modules machinery. The main motivation for this change is to create a
second implementation that uses a more efficient kind of
`llvm::AdvisoryLock` during dependency scanning.

In addition to the lock abstraction, the `ModuleCache` interface also
manages the existing `InMemoryModuleCache` instance. I found that
compared to keeping these separate/independent, the code is a bit
simpler now, since these are two tightly coupled concepts. I can
envision a more efficient implementation of the `InMemoryModuleCache`
for the single-process case too, which will be much easier to implement
with the current setup.

This is not intended to be a functional change.
2025-03-14 11:32:39 -07:00
Carlos Galvez
e4a8969e56
[clang-tidy] Avoid processing declarations in system headers (#128150)
[clang-tidy] Avoid processing declarations in system headers

Currently, clang-tidy processes the entire TranslationUnit, including
declarations in system headers. However, the work done in system
headers is discarded at the very end when presenting results, unless
the SystemHeaders option is active.

This is a lot of wasted work, and makes clang-tidy very slow.
In comparison, clangd only processes declarations in the main file,
and it's claimed to be 10x faster than clang-tidy:

https://github.com/lljbash/clangd-tidy

To solve this problem, we can apply a similar solution done in clangd
into clang-tidy. We do this by changing the traversal scope from the
default TranslationUnitDecl, to only contain the top-level declarations
that are _not_ part of system headers. We do this in the
MatchASTConsumer class, so the logic can be reused by other tools.
This behavior is currently off by default, and only clang-tidy
enables skipping system headers. If wanted, this behavior can be
activated by other tools in follow-up patches.

I had to move MatchFinderOptions out of the MatchFinder class,
because otherwise I could not set a default value for the
"bool SkipSystemHeaders" member otherwise. The compiler error message
was "default member initializer required before the end of its
enclosing class".

Note: this behavior is not active if the user requests warnings from
system headers via the SystemHeaders option.

Note2: out of all the unit tests, only one of them fails:

readability/identifier-naming-anon-record-fields.cpp

This is because the limited traversal scope no longer includes the
"CXXRecordDecl" of the global anonymous union, see:
https://github.com/llvm/llvm-project/issues/130618

I have not found a way to make this work. For now, document the
technical debt introduced.

Note3: I have purposely decided to make this new feature enabled by
default, instead of adding a new "opt-in/opt-out" flag. Having a new
flag would mean duplicating all our tests to ensure they work in both
modes, which would be infeasible. Having it enabled by default allow
people to get the benefits immediately. Given that all unit tests pass,
the risk for regressions is low. Even if that's the case, the only
issue would be false negatives (fewer things are detected), which
are much more tolerable than false positives.

Credits: original implementation by @njames93, here:
https://reviews.llvm.org/D150126

This implementation is simpler in the sense that it does not consider
HeaderFilterRegex to filter even further. A follow-up patch could
include the functionality if wanted.

Fixes #52959

Co-authored-by: Carlos Gálvez <carlos.galvez@zenseact.com>
2025-03-14 13:16:17 +01:00
Jan Voung
467ad6a035
[clang-format] Add support for absl nullability macros (#130346)
Add support for formatting w/ absl nullability macros
(c52afac4f8/absl/base/nullability.h (L237)).
Example at https://godbolt.org/z/PYv19M1Gj
input:
```
std::vector<int* _Nonnull> x;
std::vector<int* absl_nonnull> y;
```

orig output:
```
std::vector<int* _Nonnull> x;
std::vector<int * absl_nonnull> y;
```

new output:
```
std::vector<int* _Nonnull> x;
std::vector<int* absl_nonnull> y;
```
credit to @ymand for the original patch
2025-03-14 00:01:35 -07:00