Commit Graph

8542 Commits

Author SHA1 Message Date
metagn
8c9a645bdf fix generic converter regression with var/subtype args (#24902)
refs #24867,
https://github.com/nim-lang/Nim/pull/24867#issuecomment-2821315971

The argument node of the converter can be wrapped in [hidden `addr` or
subtype conversion
nodes](dc100c5caa/compiler/sigmatch.nim (L2327-L2335))
which have to be skipped when matching the type again, since the type of
the node is the uninstantiated type taken from the proc parameter.
2025-04-24 21:18:18 +02:00
Ryan McConnell
5dcfd8d7bb Add tySet to concept matching (#24908) 2025-04-24 21:17:42 +02:00
metagn
d966ee3fc3 whitelist prev types to reuse in newOrPrevType (#24899)
fixes #24898

A type is only overwritten if it is definitely a forward type, partial
object (symbol marked `sfForward`) or a magic type. Maybe worse for
performance but should be more correct. Another option might be to
provide a different value for `prev` for the `preserveSym` case but then
we cannot easily ignore only nominal type nodes.
2025-04-22 17:24:22 +02:00
metagn
7f0e07492f generally disallow recursive structural types, check proc param types (#24893)
fixes #5631, fixes #8938, fixes #18855, fixes #19271, fixes #23885,
fixes #24877

`isTupleRecursive`, previously only called to give an error for illegal
recursions for:

* tuple fields
* types declared in type sections
* explicitly instantiated generic types

did not check for recursions in proc types. It now does, meaning proc
types now need a nominal type layer to recurse over themselves. It is
renamed to `isRecursiveStructuralType` to better reflect what it does,
it is different from a recursive type that cannot exist due to a lack of
pointer indirection which is possible for nominal types.

It is now also called to check the param/return types of procs, similar
to how tuple field types are checked. Pointer indirection checks are not
needed since procs are pointers.

I wondered if this would lead to a slowdown in the compiler but since it
only skips structural types it shouldn't take too many iterations, not
to mention only proc types are newly considered and aren't that common.
But maybe something in the implementation could be inefficient, like the
cycle detector using an IntSet.

Note: The name `isRecursiveStructuralType` is not exactly correct
because it still checks for `distinct` types. If it didn't, then the
compiler would accept this:

```nim
type
  A = distinct B
  B = ref A
```

But this breaks when attempting to write `var x: A`. However this is not
the case for:

```nim
type
  A = object
    x: B
  B = ref A
```

So a better description would be "types that are structural on the
backend".

A future step to deal with #14015 and #23224 might be to check the
arguments of `tyGenericInst` as well but I don't know if this makes
perfect sense.
2025-04-21 09:01:44 +02:00
metagn
9c2593444a consider proc return type as weak reference in codegen (#24894)
fixes #7706
2025-04-21 08:58:45 +02:00
metagn
525d64fe88 leave type section symbols unchanged on resem, fix overly general double semcheck for forward types (#24888)
fixes #24887 (really just this [1 line
commit](632c7b3397)
would have been enough to fix the issue but it would ignore the general
problem)

When a type definition is encountered where the symbol already has a
type (not a forward type), the type is left alone (not reset to
`tyForward`) and the RHS is handled differently: The RHS is still
semchecked, but the type of the symbol is not updated, and nominal type
nodes are ignored entirely (specifically if they are the same kind as
the symbol's existing type but this restriction is not really needed).
If the existing type of the symbol is an enum and and the RHS has a
nominal enum type node, the enum fields of the existing type are added
to scope rather than creating a new type from the RHS and adding its
symbols instead.

The goal is to prevent any incompatible nominal types from being
generated during resem as in #24887. But it also restricts what macros
can do if they generate type section AST, for example if we have:

```nim
type Foo = int
```

and a macro modifies the type section while keeping the symbol node for
`Foo` like:

```nim
type Foo = float
```

Then the type of `Foo` will still remain `int`, while it previously
became `float`. While we could maybe allow this and make it so only
nominal types cannot be changed, it gets even more complex when
considering generic params and whether or not they get updated. So to
keep it as simple as possible the rule is that the symbol type does not
change, but maybe this behavior was useful for macros.

Only nominal type nodes are ignored for semchecking on the RHS, so that
cases like this do not cause a regression:

```nim
template foo(): untyped =
  proc bar() {.inject.} = discard
  int

type Foo = foo()
bar() # normally works
```

However this specific code exposed a problem with forward type handling:

---

In specific cases, when the type section is undergoing the final pass,
if the type fits some overly general criteria (it is not an object,
enum, alias or a sink type and its node is not a nominal type node), the
entire RHS is semchecked for a 2nd time as a standalone type (with `nil`
prev) and *maybe* reassigned to the new semchecked type, depending on
its type kind. (for some reason including nominal types when we excluded
them before?) This causes a redefinition error if the RHS defines a
symbol.

This code goes all the way back to the first commit and I could not find
the reason why it was there, but removing it showed a failure in
`thard_tyforward`: If a generic forward type is invoked, it is left as
an unresolved `tyGenericInvocation` on the first run. Semchecking it
again at the end turns it into a `tyGenericInst`. So my understanding is
that it exists to handle these loose forward types, but it is way too
general and there is a similar mechanism `c.skipTypes` which is supposed
to do the same thing but doesn't.

So this is no longer done, and `c.skipTypes` is revamped (and renamed):
It is now a list of types and the nodes that are supposed to evaluate to
them, such that types needing to be updated later due to containing
forward types are added to it along with their nodes. When finishing the
type section, these types are reassigned to the semchecked value of
their nodes so that the forward types in them are fully resolved. The
"reassigning" here works due to updating the data inside the type
pointer directly, and is how forward types work by themselves normally
(`tyForward` types are modified in place as `s.typ`).

For example, as mentioned before, generic invocations of forward types
are first created as `tyGenericInvocation` and need to become
`tyGenericInst` later. So they are now added to this list along with
their node. Object types with forward types as their base types also
need to be updated later to check that the base type is correct/inherit
fields from it: For this the entire object type and its node are added
to the list. Similarly, any case where whether a component type is
`tyGenericInst` or `tyGenericInvocation` matters also needs to cascade
this (`set` does presumably to check the instantiated type).

This is not complete: Generic invocations with forward types only check
that their base type is a forward type, but not any of their arguments,
which causes #16754 and #24133. The generated invocations also need to
cascade properly: `Foo[Bar[ForwardType]]` for example would see that
`Bar[ForwardType]` is a generic invocation and stay as a generic
invocation itself, but it might not queue itself to be updated later.
Even if it did, only the entire type `Foo[Bar[ForwardType]]` needs to be
queued, updating `Bar[ForwardType]` by itself would be redundant or it
would not change anything at all. But these can be done later.
2025-04-21 07:56:14 +02:00
lit
8bc8d40778 fix(docgen): export for imported symbols missing; closes #24890 (#24891) 2025-04-20 21:22:03 +02:00
metagn
032da90ed1 implement parser for new case objects (#24885)
refs https://github.com/nim-lang/RFCs/issues/559

Parses as an `nkIdentDefs` with an `nkEmpty` name. Pragma is allowed,
can remove this if necessary.

Fine to close and postpone for later
2025-04-18 05:34:21 +02:00
metagn
5aaba213d4 account for invalid data in enum $ on arc/orc (#24886)
closes #24875

Refc gives `0 (invalid data!)`, but since enum `$` procs on arc are
generated during enum declarations we might not have access to string
concatenation and integer `$`, so it generates a static string. Just
chose an empty string for this.
2025-04-18 05:32:49 +02:00
metagn
3d14381473 fix stmtlist expression indent regression (#24883)
follows up #24855

Before #24855, the test would work because the indentation of the `;`
token would be passed to `semiStmtList` and so its indentation of `-1`
would be used. Now the `;` token is skipped and the indentation of the
first `discard` is used which is > -1. However the second discard has an
indentation of -1 because it's on the same line: this fails the
`sameInd(p) or realInd(p)` check since -1 is never >= the indent of the
first discard.

For compatibility with the parser up to this point this indent check is
entirely removed, meaning the indent is ignored. Because the `;` is
basically never on a separate line, this was already the case for
basically every use. `semiStmtList` is wrapped in a `withInd` anyway
which resets the indent after it's done, since the entire statement list
is wrapped in a `()`. To disallow dedents, the above check could be
fixed to use `sameOrNoInd` instead of `sameInd`, which is done in the
commented version of this check.
2025-04-17 00:44:31 +03:00
ringabout
9f359e8d6d fixes #24879; Data getting wiped on copy with iterators and =copy on refc (#24880)
fixes #24879
2025-04-16 22:51:12 +02:00
Juan M Gómez
e7f73bfebe Fixes a nimsuggest crash (#24873) 2025-04-16 12:11:33 +02:00
metagn
c06bb6cc03 don't traverse inner procs to lift locals in closure iters (#24876)
fixes #24863, refs #23787 and #24316

Working off the minimized example, my understanding of the issue is: `n`
captures `r` as `:envP.r1` where `:envP` is the environment of `b`, then
`proc () = n()` does the lambda lifting of `n` again (which isn't done
if the `proc ()` is marked `{.closure.}`, hence the workaround) which
then captures the `:envP` as another field inside the `:envP`, so it
generates `:envP.:envP_2.r1` but the `.:envP_2` field is `nil`, so it
causes a segfault.

The problem is that the capture of `r` in `n` is done inside
`detectCapturedVars` for the surrounding closure iterator: inner procs
are not special cased and traversed as regular nodes, so it thinks it's
inside the iterator and generates a field access of `:envP` freely. The
lambda lifting version of `detectCapturedVars` ignores inner procs and
works off of symbol uses (anonymous iterator and lambda declarations
pretend their symbol is used).

As a naive solution, closure iterators now also ignore inner proc
declarations same as `lambdalifting.detectCapturedVars`, but unlike it
they also don't do anything for the inner proc symbols. Lambdalifting
seems to properly handle the lifted variables but in the worst case we
can also make sure `closureiters.detectCapturedVars` traverses inner
procs by marking every local of the closure iter used in them as needing
lifting (but not doing the lifting). This does not seem necessary for
now so it's not done (was done and reverted in [this
commit](9bb39a9259)),
but regressions are still possible
2025-04-15 19:29:46 +02:00
metagn
4d9e5e8b6d fix field setter fallback that never worked (#24871)
refs https://forum.nim-lang.org/t/12785, refs #4711

The code was already there that when `propertyWriteAccess` returns `nil`
(i.e. cannot find a setter), `semAsgn` turns the [LHS into a call and
semchecks
it](1ef9a656d2/compiler/semexprs.nim (L1941-L1948)),
meaning if a setter cannot be found a getter will be assigned to
instead. However `propertyWriteAccess` never returned nil, because
`semOverloadedCallAnalyseEffects` was not called with `efNoUndeclared`
and so produced an error directly. So `efNoUndeclared` is passed to this
call so this code works as intended.

This fixes the issue described in #4711 which was closed because
subscripts do not have the same behavior implemented. However we can
implement this for subscripts as well (I have an implementation ready),
it just changes the error message from the failed overloads of `[]=` to
the failed overloads of `[]` for the LHS, which might be misleading but
is consistent with the error messages for any other assignment. I can do
this in this PR or another one.
2025-04-13 19:21:33 +02:00
metagn
1ef9a656d2 allow setting arbitrary size for importc types (#24868)
split from #24204, closes #7674

The `{.size.}` pragma no longer restricts the given size to 1, 2, 4 or 8
if it is used for an imported type. This is not tested very thoroughly
but there's no obvious reason to disallow it.
2025-04-12 17:55:11 +02:00
metagn
334f96c05a isolate and rematch generic converters to get bindings (#24867)
fixes #4554, fixes #10900, fixes #13843, fixes #19471, fixes #19517

Instead of matching generic converters to their arguments using the full
call match bindings, a new match is created for them (from which the
bindings are used to instantiate the converter return type). Then when
instantiating generic converters, they are matched to their argument
again to get their bindings again instead of using the call bindings.
This prevents generic converters which match more than once from
interfering with each other's bindings.
2025-04-12 17:53:18 +02:00
Jake Leahy
0cba752c8a Allow specifiying path to use for stdin error messages (#24595)
Implements #24569

Adds `--stdinfile` flag for specifying the file to use in place of
`stdinfile.nim` in error messages. Will enable easier integration of
tooling with nim check
2025-04-12 08:40:25 +02:00
metagn
4d075dc301 clean up opensym encounters in compiler (#24866)
To protect against crashes when this stops being experimental, in most
places handled the exact same as normal symchoices (not encountered in
typed ast)
2025-04-12 08:39:11 +02:00
ringabout
42df731a2d fixes #24764; cross-module sink analysis broken (#24862)
fixes  #24764

It now consumes the `conv(x)` arg for the explicit sinking. So the
explicit sinking is kept as it is.

Follows up https://github.com/nim-lang/Nim/pull/20585

Related issues: https://github.com/nim-lang/Nim/issues/20572

Probably the same needs to be applied to explicit `copy` to prevent a
copy turning into a sink
2025-04-12 06:47:57 +02:00
metagn
f58cd51fc4 ignore typeof in closure iterators (#24861)
fixes #24859
2025-04-11 23:50:13 +03:00
metagn
897126a711 fix array/set/tuple literals with generic expression elements (#24497)
fixes #24484, fixes #24672

When an array, set or tuple constructor has an element that resolves to
`tyFromExpr`, the type of the entire literal is now set to `tyFromExpr`
and the subsequent elements are not matched to any type.

The remaining expressions are still typed (a version of the PR before
this called `semGenericStmt` on them instead), however elements with int
literal types have their types set to `nil`, since generic instantiation
removes int literal types and the int literal type is required for
implicitly converting the int literal element to the set type. Tuples
should not really need this but it is done for them anyway in case it
messes up some type inference

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
2025-04-11 18:38:35 +02:00
Ryan McConnell
d4098e6ca0 new-style concept bugfix (#24858)
Combining two small PRs in one here. The test case explains what was
wrong with the concepts and for naitivesockets, it's typical to adjust
`ai_flags` so I opened that up.
2025-04-11 06:54:52 +02:00
metagn
918f972369 skip semicolon in stmtlist expr parsing (#24855)
Previously it would try to parse the semicolon as its own statement and
produce an `nkEmpty` node

Also more than 1 semicolon in an expression list i.e. `(a;; b)` gives an
"expression expected" error in `semiStmtList` when multiple semicolons
are allowed in normal statements, this could be fixed by changing the
`if tok.kind == tokSemicolon` check to a `while` but it does not match
the grammar so not done here.
2025-04-11 03:29:20 +02:00
ringabout
51166ab382 fixes =copy is transformed into nkFastAsgn and unify mAsgn handling (#24857)
`=copy` should be treated like `=` instead of `shallowCopy`, i.e.,
`nkFastAsgn` by default. `mAsgn` is treated similar in sempass2 too
2025-04-11 03:28:53 +02:00
ringabout
40a1ec21d7 overhaul hook injections (#24841)
ref https://github.com/nim-lang/Nim/issues/24764 

To keep destructors injected consistently, we need to transform `mAsgn`
properly into `nkSinkAsgn` and `nkAsgn`. This PR is the first step
towards overhauling hook injections.

In this PR, hooks (except mAsgn) are treated consistently whether it is
resolved in matching or instantiated by sempass2. It also fixes a
spelling `=wasMoved` to its normalized version, which caused no
replacing generic hook calls with lifted hook calls.
2025-04-10 09:24:19 +02:00
ringabout
29a2e25d1e fixes #24850; macro-generated if/else and when/else statements have m… (#24852)
…ismatched indentation with repr

fixes #24850
2025-04-08 23:54:31 +08:00
metagn
a625fab098 make fillObjectFields recur over base type (#24854)
fixes #24847

Object constructors call `fillObjectFields` when a field inside the
constructor does not have a location, however when the field is from a
base type this does not process it. Now `fillObjectFields` also calls
itself for the base type to fix this but not sure if this is a good
solution as `fillObjectFields` is used in other places too.
2025-04-08 17:00:58 +03:00
ringabout
26b86c8f4d Makes except: panics on Defect (#24821)
implements https://github.com/nim-lang/RFCs/issues/557


It inserts defect handing into a bare except branch

```nim
try:
  raiseAssert "test"
except:
  echo "nope"
```

=>

```nim
try:
  raiseAssert "test"
except:
  # New behaviov, now well-defined: **never** catches the assert, regardless of panic mode
  raiseDefect()
  echo "nope"
```

In this way, `except` still catches foreign exceptions, but panics on
`Defect`. Probably when Nim has `except {.foreign.}`, we can extend
`raiseDefect` to foreign exceptions as well. That's supposed to be a
small use case anyway.

 `--legacy:noPanicOnExcept` is provided for a transition period.
2025-04-03 16:09:58 +02:00
ringabout
73aeac81d1 fixes #24806; don't elide wasMoved when syms are used in blocks (#24831)
fixes #24806

Blocks don't merge symbols that are used before destruction to the
parent scope, which causes `wasMoved; destroy` to elide incorrectly
2025-04-03 12:54:00 +02:00
metagn
5bcd9a329a fix infinite recursion with pushed user pragmas (#24839)
fixes #24838
2025-04-03 12:53:42 +02:00
ringabout
4352fa2ef0 fixes #24801; Invalid C codegen generated when destroying distinct seq types (#24835)
fixes #24801

Because distinct `seq` types match `proc `=destroy`*[T](x: var T)
{.inline, magic: "Destroy".}`. But the Nim compiler generates lifted seq
types for corresponding distinct types. So we skip the address for
distinct types.

Related to https://github.com/nim-lang/Nim/pull/22207 I had a hard time
finding the other place where generic destructors get replaced by
attachedDestructors
2025-04-02 18:46:29 +02:00
ringabout
3617d2e077 fixes lastRead uses the when nimvm branch (#24834)
```nim
proc foo =
  var x = "1234"
  var y = x
  when nimvm:
    discard
  else:
    var s = x
    doAssert s == "1234"
  doAssert y == "1234"

static: foo()
foo()
```
`dfa` chooses the `nimvm` branch, `x` is misread as a last read and
`wasMoved`.

`injectDestructor` is used for codegen and is not used for vmgen. It's
reasonable to choose the codegen path instead of the `nimvm` path so the
code works for codegen. Though the problem is often hidden by
`cursorinference` or `optimizer`.

found in https://github.com/nim-lang/Nim/pull/24831
2025-04-02 09:29:15 +02:00
ringabout
f9c8775783 conv needs to be picky about aliases and introduces a temp for addr conv (#24818)
ref https://github.com/nim-lang/Nim/pull/24817
ref https://github.com/nim-lang/Nim/pull/24815
ref https://github.com/status-im/nim-eth/pull/784


```nim
{.emit:"""
void foo(unsigned long long* x)
{
}
""".}


proc foo(x: var culonglong) {.importc: "foo", nodecl.}

proc main(x: var uint64) =
  # var s: culonglong = u # TODO:
  var m = uint64(12)
  # var s = culonglong(m)
  foo(culonglong m)

var u = uint64(12)
main(u)
```
Notes that this code gives incompatible errors in 2.0.0, 2.2.0 and the
devel branch. With this PR, `conv` is kept, but it seems to go back to
https://github.com/nim-lang/Nim/pull/24807
2025-04-01 09:37:54 +02:00
ringabout
58b1f28177 fixes implicitConv discarding flags (#24817)
follow up https://github.com/nim-lang/Nim/pull/24809
ref https://github.com/nim-lang/Nim/pull/24815
2025-03-28 12:52:45 +01:00
ringabout
73112d64a3 fixes #24793; Revert "remove special treatments of sinking const sequences (#24812)
fixes #24793

There doesn't seem to have a better solution
2025-03-26 23:49:00 +08:00
ringabout
ddd83f8d8a fixes #24800; Invalid C code generation with a method, case object in refc (#24809)
fixes #24800

This PR avoids a conversion from `sink T` to `T`

I will add a test case
2025-03-25 20:42:40 +01:00
lit
d573578b28 repl: support eof, define object with fields (#24784)
For `nim secret`:

- **fix(repl): eof(ctrl-D/Z) and ctrl-C were ignored**
- **feat(repl): continueLine  figures section, constr, bool ops**

---------

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
2025-03-25 07:41:17 +01:00
metagn
fcba14707a disable "dest register is set" for vm statements (#24797)
closes #24780

This proc `genStmt` is only called to run the VM in `vm.evalStmt`,
otherwise it's not used in vmgen. Now it acts the same as `proc
gen(PCtx, PNode)`, used by `discard` statements, which just calls
`freeTemp` on the dest if it was set rather than erroring.
2025-03-23 06:59:06 +03:00
ringabout
7c5d005510 fixes #10625; setjmp on linux mangles ebp leading to early collection (#24787)
fixes #10625
2025-03-18 18:51:34 +08:00
Ryan McConnell
2b699bca53 new-style concepts - small bugfix (#24778) 2025-03-15 15:05:14 +01:00
metagn
fb93295344 fix compound inheritance penalty (#24775)
fixes #24773

`c.inheritancePenalty` is supposed to be used for the entire match, but
in these places the inheritance penalty of a single argument overrides
the entire match penalty. The `+ ord(c.inheritancePenalty < 0)` is
copied from other places that use the same idiom, the intent is that the
existing penalty changes from -1 to 0 first to mark that it participates
in inheritance before adding the inheritance depth.

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2025-03-12 17:31:33 +01:00
ringabout
9ebfa7973a fixes generic types sink T cannot be inferred for passed arguments (#24761)
Otherwise, `sink T` is kept as it is. This PR treats sink types as its
base types for the arguments. So the concept would match both cases

Required by https://github.com/nim-lang/Nim/pull/24724
2025-03-12 17:31:19 +01:00
ringabout
dfa482e292 fixes #24770; Thread local not registed as GC root when =destroy exists (#24776)
fixes #24770

e.g. `seq[(ObjectWithDestructors, string)]`/ For refc, a seq with
elements that have destructors will have `hasAsgn` flags. The flag is
the criteria whether a seq is thought as `containsGarbageCollectedRef`.
i.e. whether to `registerTraverseProc` for the type.
The culprit seems to be that `searchTypeForAux` doesn't consider the
element type of sequence, even it contains a string that should belong
to `GarbageCollectedRef`.

With this PR:

It now generates

```
nimRegisterThreadLocalMarker(TM__mSF73dT1lSI7DG58StKHLQ_5);
```

in refc
2025-03-12 17:29:30 +01:00
metagn
82891e6850 give hint for forward declarations with unknown raises effects (#24767)
refs #24766

Detect when we track a call to a forward declaration without explicit
`raises` effects, then when the `raises` check fails for the proc, give
a hint that this forward declaration was tracked as potentially raising
any exception.
2025-03-11 14:24:45 +01:00
Ryan McConnell
850f327713 folding const expressions with branching logic (#24689)
motivating example:
```nim
iterator p(a: openArray[char]): int =
  if a.len != 0:
    if a[0] != '/':
      discard
for t in p(""): discard
```
The compiler wants to evaluate `a[0]` at compile time even though it is
protected by the if statement above it. Similarly expressions like
`a.len != 0 and a[0] == '/'` have problems. It seems like the logic in
semfold needs to be more aware of branches to positively identify when
it is okay to fail compilation in these scenarios. It's a bit tough
though because it may be the case that non-constant expressions in
branching logic can properly protect some constant expressions.
2025-03-11 10:01:32 +01:00
metagn
38ad336c69 fix tuple nodes from VM inserting hidden conv to keep old type (#24756)
fixes #24755, refs #24710

Instead of using the node from `indexTypesMatch` which inserts a hidden
conv node, just change the type of the node back to the old type
directly
2025-03-11 10:00:37 +01:00
ringabout
a7711d452d fixes #24754; {.gcsafe.} block breaks move analysis (#24757)
fixes #24754
2025-03-11 09:59:55 +01:00
metagn
e2e7790779 fix canRaise for non-proc calls (#24752)
fixes #24751

`typeof` leaves the object constructor as a call node for some reason,
in this case it tries to access the first child of the type node but the
object has no fields so the type field is empty. Alternatively the
optimizer can stop looking into `typeof`
2025-03-11 09:59:21 +01:00
Michael Lee
dfd2987118 Add linking options for tinycc backend (#24750)
### Issue

When using `tcc` as backend to compile a trivial program

```
nim c  --cc:tcc  --skipCfg a.nim
```

, errors reported:

```
tcc: error: undefined symbol 'fabs'
```

### Solution

`fabs` belongs to libm. With these two options added, one can compile
with an additional clib option:

```
nim c  --cc:tcc  --skipCfg --clib:m a.nim
```
2025-03-11 09:58:22 +01:00
ringabout
e2d4791229 fixes move for getPotentialWrites (#24753)
`move` would modify parameters as well
2025-03-11 09:57:48 +01:00