Compare commits

..

57 Commits

Author SHA1 Message Date
ringabout
2c9be48edf fix(types): refine alias skipping logic in type comparison functions 2026-05-23 20:28:25 +08:00
ringabout
c31121ec3b refactor: simplify type comparison logic by removing unnecessary type skips 2026-05-23 18:43:26 +08:00
ringabout
49d35670b1 Merge branch 'devel' into pr_orc 2026-05-21 20:07:26 +08:00
ringabout
43ac102ca8 fixes #25800; move now uses its declaration for overridden =wasMoved (#25809)
fixes #25800
closes https://github.com/nim-lang/Nim/pull/25807
ref https://github.com/nim-lang/Nim/issues/25800

This pull request improves the handling of move semantics and the
`=wasMoved` hook in the Nim compiler, especially for C++ code generation
and user-defined types. It refactors the move operation logic to better
support custom hooks, adds new tests for edge cases, and ensures that
the `move` operation is safer and more predictable.

**Move semantics and `=wasMoved` handling:**

* Refactored the move operation in `compiler/ccgexprs.nim` by
introducing helper procs (`canGenMoveCall`, `genMoveCall`,
`genWasMovedCall`, `genMoveWithWasMoved`) to better handle cases with
user-defined `=wasMoved` hooks, especially for generics and C++ interop.
The logic now distinguishes between simple assignments and when to call
custom hooks, improving correctness and maintainability.
[[1]](diffhunk://#diff-4509107d295d7d32b1887c8993cd0f56113ae60f36113e7d8778646dabd92ebcL2818-R2851)
[[2]](diffhunk://#diff-4509107d295d7d32b1887c8993cd0f56113ae60f36113e7d8778646dabd92ebcL2841-R2882)
* Updated the `move` proc in `lib/system.nim` to include the `nodestroy`
pragma, preventing double destruction and making move semantics safer.

**Testing and validation:**

* Added a new test (`tests/ccgbugs2/t25800.nim`) to ensure that
user-defined `=wasMoved` hooks with `{.importcpp.}` are correctly
generated and invoked in C++ code, addressing a specific bug with
invalid preprocessor directives.
* Expanded `tests/destructor/twasmoved.nim` with additional test cases
for objects with and without custom `=wasMoved` hooks, including
multithreaded scenarios using `threadpool`, to verify correct behavior
in a variety of contexts.

**Minor cleanup:**

* Added a blank line for code style consistency in
`compiler/semmagic.nim`.
2026-05-21 13:42:38 +02:00
Rybnikov Alex
393d27b57d fix(stdlib): use first-element flag in $ for collections (#18583) (#25832)
Fixes #18583.

## Problem

Several stdlib collection types compute the separator for `$` using
`result.len > 1`, where `result` starts as the opening bracket (`"["` or
`"{"`). This breaks when a collection element type has a `$` that
returns an empty string: `result.len` stays at 1 after the first item
contributes nothing, so the separator is never inserted for subsequent
items.

```nim
import std/deques

type Test = object
proc `$`(x: Test): string = ""

echo [Test(), Test()].toDeque  # prints [] — expected [, ]
```

## Fix

Replace the length check with an explicit `first` flag in all affected
modules: `deques`, `heapqueue`, `lists`, `critbits`, and `strtabs`.

## Tests

Regression tests added to `tdeques`, `theapqueue`, and `tlists` using a
local type whose `$` returns `""`. All three test files pass with `nim c
-r`.

## Notes

I work with Claude as a co-processor. I'm 56, came to programming late,
and this is genuinely how I learn and contribute. I understand what I'm
submitting, but I didn't write it alone. If your project prefers
human-only contributions, just say so and I'll close without friction.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:40:35 +02:00
Andreas Rumpf
9f5c193c1d fixes #25814 (#25816) 2026-05-19 23:28:13 +02:00
Pedro Batista
4f6b727d9e pegs: accept UTF-8 bytes in bare identifier terminals (#25829)
## Summary
- Fixes `std/pegs` lexing for bare UTF-8 terminals such as `\i café`.
- The lexer previously stopped at the first non-ASCII byte, so
`pkTerminalIgnoreCase` never saw the full term despite its rune-aware
`fastRuneAt`/`toLower` matching.
- This now keeps non-ASCII bytes in identifier-style terminals while
ASCII non-ident characters still terminate the symbol.

## Behavior
Before: `match("CAFÉ", peg"\i café")` failed because the terminal was
lexed as `caf`.
After: `match("CAFÉ", peg"\i café")`, `match("Café", peg"\i café")`, and
`findAll` over mixed-case occurrences pass.

`std/pegs` documents `useUnicode = true` as proper UTF-8 support, and
quoted terminals already preserved the same bytes; this makes bare
terminals consistent with that path.

I did not find an existing relevant issue or PR in searches for
pegs/unicode/utf8/getSymbol/pkTerminalIgnoreCase.
2026-05-19 23:27:48 +02:00
ringabout
f9647276d8 fixes #25821; unary minus off by one mistake [backport] (#25823)
fixes #25821

This pull request includes a minor bug fix in the lexer and adds new
test cases for string formatting with binary operators in interpolated
expressions.

Lexer bug fix:

* Fixed an off-by-one error in the unary minus detection logic in the
`rawGetTok` procedure in `lexer.nim`, ensuring that the start-of-buffer
condition is correctly checked.

Testing improvements:

* Added tests to `tstrformat.nim` to verify that binary operators (such
as subtraction) work correctly inside interpolated string expressions
using both `&` and `fmt`.
2026-05-18 07:55:33 +02:00
vip892766gma
2c946950f4 fix: duplicated "to" in alloc.nim comments (#25813)
Two one-line typo fixes for duplicated "to" in `lib/system/alloc.nim`:
- "# set 'used' to to true:" → "# set 'used' to true:" (occurs twice,
lines ~694 and ~711)

No code/behavior change.

Co-authored-by: Aiden Park <275402320+vip892766gma@users.noreply.github.com>
2026-05-14 08:02:36 +02:00
oab24413gmai
bbc5bbdcc7 fix: duplicated words in manual.md and gc_common.nim comment (#25812)
Two one-line typo fixes for duplicated words:
- `doc/manual.md` — "if the the type was marked as `bycopy`" → "if the
type was marked as `bycopy`"
- `lib/system/gc_common.nim` — "## thread stack is is returned." → "##
thread stack is returned."

No code/behavior change.

Co-authored-by: Mira Sato <275437409+oab24413gmai@users.noreply.github.com>
2026-05-14 08:02:11 +02:00
Andreas Rumpf
6204e48ba5 SSO strings: bugfix (#25810) 2026-05-12 23:20:10 +02:00
Nils-Hero Lindemann
f0c60b06e5 Update outdated string representation in example (#25802)
See
[here](https://nim-lang.github.io/Nim/tut1.html#internal-type-representation).
2026-05-09 08:55:39 +02:00
Ryan McConnell
4c8052a45b fix: implicit imports drop std/ prefix (#25780)
Preserves implicit imports instead of always storing the resolved
absolute filename. That lets the later StdPrefix warning check see the
original std/objectdollar spelling.

This is for situations where in cfg or cli warnings are enabled for the
prefix. Essentially a niche combination of compiler switches don't get
along e.g.

`-d:nimPreviewSlimSystem --warning:StdPrefix:on
--warningAsError:StdPrefix:on --import:std/objectdollar`

will cause:

`Error: objectdollar needs the 'std' prefix [StdPrefix]`
2026-05-08 06:50:13 +02:00
Nils-Hero Lindemann
7295f57833 Write all variables italic in section "About this document" (#25797)
Makes more sense. One variable was already written italic.
2026-05-08 06:48:48 +02:00
ringabout
065f46afa8 fixes #23765; Method calling proc with return value overlapping declared generic type generates invalid C 2026-05-06 21:33:46 +08:00
Andreas Rumpf
f0077a12b2 fixes DOS via malformed HTTP protocol (#25793)
refs https://github.com/nim-lang/Nim/pull/25568
2026-05-06 19:48:08 +08:00
ringabout
568eccd7f8 fixes #25617; handle backend type aliasing in procParamTypeRel (#25692)
fixes #25617

This pull request introduces a stricter check for parameter type
relations in the `procParamTypeRel` procedure. Specifically, it ensures
that two types are not only structurally equal but also have the same
backend type, taking type aliases into account.

Type relation checks:

*
[`compiler/sigmatch.nim`](diffhunk://#diff-251afcd01d239369019495096c187998dd6695b6457528953237a7e4a10f7138R787-R789):
In `procParamTypeRel`, added a check to ensure that if two types are
considered equal (`isEqual`), they must also have the same backend type
(using `sameBackendTypePickyAliases`). If not, the result is set to
`isNone`, preventing false positives when type aliases differ.
2026-05-06 08:44:09 +02:00
ringabout
f2e4ae0016 fixes lent tuple codegen error (#25782)
ref https://github.com/nim-lang/Nim/pull/25783

This pull request addresses an issue with addressability of tuple
elements of type `lent` or `var` in Nim, ensuring that expressions
involving these types are handled correctly during type changes. The
main changes introduce a check to prevent attempting to change the type
of tuple elements that are views (`var` or `lent`), and a new test is
added to verify the correct error is raised when trying to take the
address of such elements.

Type system and semantic analysis improvements:

* Added the `isViewTarget` template in `semexprs.nim` to check if a type
is a view (`var` or `lent`), and updated `changeType` to skip type
changes for tuple elements that are views. This prevents invalid
addressability operations on these types.
[[1]](diffhunk://#diff-539da3a63df08fa987f1b0c67d26cdc690753843d110b6bf0805a685eeaffd40R655-R657)
[[2]](diffhunk://#diff-539da3a63df08fa987f1b0c67d26cdc690753843d110b6bf0805a685eeaffd40R686-R693)

Testing:

* Added a new test `tlent_tuple_address.nim` to verify that attempting
to take the address of tuple elements of type `lent` correctly produces
an "expression has no address" error.
2026-05-06 08:42:36 +02:00
Andreas Rumpf
df7a114d7a IC: use the newer nif27 format (#25792) 2026-05-06 08:41:59 +02:00
ringabout
e9a0c9634e fixes #25784; Object default field initialized with an object constructor (#25785)
fixes #25784

This pull request addresses the handling of forward object types during
type determination in the Nim compiler and adds new test cases to ensure
correct default value initialization for objects with forward
references. The main focus is to allow forward object types to remain
unresolved during the initial type analysis, deferring their resolution
to a later compilation phase. This helps support object constructors
with default values involving forward types.

**Compiler improvements:**

* Updated `semObjConstr` in `compiler/semobjconstr.nim` to allow forward
object types (`tyForward`) to remain unresolved during determine-type
analysis. This avoids premature errors and ensures that such types are
resolved later, supporting delayed field-default resolution.

**Testing enhancements:**

* Added new test cases in `tests/objects/mobject_default_value.nim` to
verify that objects with default fields referencing forward types are
correctly initialized, and that their default values are properly set.

---------

Co-authored-by: Copilot <copilot@github.com>
2026-05-05 21:05:17 +02:00
ringabout
b73908a361 fix #25789; improve handling of distinct types (#25791)
fix #25789

This pull request addresses an issue with the `distinctBase` trait in
the Nim compiler, ensuring it correctly handles types with generic
parameters and static parameters. Additionally, it adds a new test to
cover this scenario. The most important changes are:

### Compiler logic improvements

* Updated the `evalTypeTrait` implementation for the `distinctBase`
trait in `compiler/semmagic.nim` to properly skip all relevant type
wrappers, including those with generic and static parameters, when
unwrapping distinct types. This fixes incorrect handling of types like
`distinct L[int, 100]`.

### Test coverage

* Added a new test block for bug #25789 in
`tests/metatype/ttypetraits.nim` that defines a distinct type over a
generic type with a static parameter, verifies conversions, and checks
that the `distinctBase` trait returns the correct type.
2026-05-05 20:21:37 +02:00
puffball1567
cbe02aa9de fixes finally being skipped when except T as e re-raises (cpp backend) (#25775)
## Bug

When an `except T as e:` handler in the cpp backend raises a new
exception, the enclosing `finally` block is silently dropped under
`--mm:arc` and `--mm:orc`:

```nim
proc main() =
  try:
    try:
      raise newException(CatchableError, "orig")
    except CatchableError as e:
      echo "inner: ", e.msg
      raise newException(CatchableError, "re:" & e.msg)
    finally:
      echo "finally"
  except CatchableError as outer:
    echo "outer: ", outer.msg

main()
```

Expected output:
```
inner: orig
finally
outer: re:orig
```

Actual output on `nim cpp --mm:arc` (and `--mm:orc`):
```
inner: orig
outer: re:orig
```

The `finally` line is missing. The bug is specific to memory managers
that use destructor injection (arc/orc); under `--mm:refc` the original
code path works correctly because no destructor wrapper is injected.

## Root cause

When the body of `except T as e:` is processed under ARC/ORC, the
destructor injection pass injects a compiler-generated `nkHiddenTryStmt`
wrapper around the handler body to call `=destroy` on `e` when it goes
out of scope. That wrapper sits at the top of `p.nestedTryStmts` with
`inExcept = false`.

`finallyActions` (which inlines the user-finally body before a raise
propagates) only inspected the topmost entry of `nestedTryStmts`.
Because the wrapper has `inExcept = false`, the check short-circuited
and the user's finally was never inlined.

After the raise, C++'s rule that sibling catch clauses do not catch each
other's throws means the surrounding `catch(...)/finally` emitted by
`genTryCpp` never runs either, so the user's finally is silently
dropped.

## Fix

- Add an `isHidden` flag to `nestedTryStmts` entries, set to `t.kind ==
nkHiddenTryStmt` so compiler-injected try wrappers can be distinguished
from user-written ones.
- In `finallyActions`, walk past `isHidden` wrappers but stop at the
first user try. If that user try is in its except branch with a finally,
inline the finally body before the raise; otherwise leave the raise
untouched (the raise will be caught by that user try's own except
branches and the inner finally will run via normal unwinding, which is
what already happens correctly under refc).

Walking past wrappers fixes the `as e` case under arc/orc. Stopping at
user trys preserves the existing correct behaviour for nested
try/except/finally constructs (e.g. `tests/exception/tfinally.nim`'s
`nested_finally`), which would otherwise see the outer finally inlined
too eagerly when an inner raise is processed.

## Tests

Adds `tests/exception/tcpp_handler_raise_finally.nim` covering:

- `except T as e:` re-raise + outer finally
- typeless `except:` re-raise + outer finally
- try/finally without except (exception propagation through finally)

The test runs on `--mm:arc`, `--mm:orc`, and `--mm:refc`.

Locally verified on both `devel` and `version-2-2`:

- `tests/exception/` — 42 PASS, 0 FAIL, 3 SKIP
- `tests/destructor/` — all PASS
- `tests/cpp/` — all PASS (single unrelated failure: `tasync_cpp.nim`
needs the `jester` package)
- `megatest` — PASS for both `--mm:arc` and `--mm:refc`, including the
previously regressing `tfinally.nim`'s `nested_finally`

## Backport

Tagged `[backport]` in the commit message for inclusion in
`version-2-2`.

---------

Co-authored-by: puffball1567 <17452514+puffball1567@users.noreply.github.com>
2026-05-05 21:27:33 +08:00
Andreas Rumpf
4bcb706d49 IC: added support for conditional dependencies (#25770) 2026-04-28 18:48:41 +02:00
ringabout
92d0c097e5 fixes #25140; Cannot resolve pragmas when new type is defined from typeof expression (#25764)
fixes #25140
2026-04-27 18:47:45 +02:00
Andreas Rumpf
49b5e66d3a SSO: better switch to enable it (#25772) 2026-04-27 18:00:29 +02:00
Zoom
cbe8ce59ed fix string setLenUninit growth without realloc for refc (#25767)
`setLenUninit(string)` was broken on the legacy refc backend when
growing within existing spare capacity.

`setLengthStrUninit` in `lib/system/sysstr.nim` only updated len when it
had to reallocate or when shrinking.

If oldLen < newLen <= capacity, it returned early without finalizing:

```nim
var s = newStringOfCap(10)
s.add("abc")
s.setLenUninit(6)
doAssert s.len == 6 # used to fail, len stayed 3
```

This escaped `tests/stdlib/tstring.nim` because the testing routine
`checkSetLenUninit` mostly resizes strings created at **exact**
length/capacity, so growth usually took the reallocating branch.

The new regression test covers the missing edge case.

So sorry for catching this only on the day of the stable release! In my
defense, the original PR hung in limbo for quite a while and it didn't
spend enough time in devel after the merge.
2026-04-25 12:27:13 +02:00
Jake Leahy
148e82f418 Add Nix certificate path to ssl_certs.nim (#25763)
This makes it easier to run Nix built containers for Nim programs since
by default Nim doesn't search environment variables for SSL certs so its
a little annoying having to move around files

-
10e7ad5bbc/pkgs/by-name/ca/cacert/package.nix (L85)
2026-04-24 14:04:30 +02:00
Tomohiro
8b44b9d9ae fixes #23668; Create a new std/nre2 module using Nim Regex replaces re and nre (#25696)
std/nre2 is implemented using https://github.com/nitely/nim-regex.
std/nre2 has almost same features as std/nre but some regular
expressions supported by std/nre are not supported.
The syntax of regular expressions of Nim Regex is explained in:
https://nitely.github.io/nim-regex/regex.html
2026-04-22 08:06:03 +02:00
ringabout
efacf1f390 Fix typo in getContentType function in cgi.nim (#25757)
This pull request fixes a typo in the `getContentType` function in
`lib/pure/cgi.nim`, ensuring it retrieves the correct `CONTENT_TYPE`
environment variable.

> Exact spelling matters: It is CONTENT_TYPE, not CONTENT_Type or
Content-Type. Environment variables in CGI are case-sensitive.
2026-04-21 16:38:56 +02:00
ringabout
60bb9c75cc fixes #25650; nim ic import std/strbasics (#25760)
fixes #25650

This pull request refactors and improves the dependency resolution logic
in the Nim compiler, The most important changes are grouped below:

### Dependency Resolution Refactor

* Replaced the `resolveFile` procedure with two more specialized
procedures: `resolveImport` (which uses the compiler's module lookup
rules for imports) and `resolveInclude` (which resolves includes
relative to the including file or search paths). Updated all usages
accordingly, improving clarity and correctness of dependency handling.
[[1]](diffhunk://#diff-1203947eecb9ef641ce7ee029677f875eb983de050b82c65ca286517feed00e6L82-R94)
[[2]](diffhunk://#diff-1203947eecb9ef641ce7ee029677f875eb983de050b82c65ca286517feed00e6L106-R103)
[[3]](diffhunk://#diff-1203947eecb9ef641ce7ee029677f875eb983de050b82c65ca286517feed00e6L121-R118)
* Removed the unused `strutils` import from `compiler/deps.nim` for
cleaner dependencies.


### Testing Improvements

* Added `import std/strbasics` to `tests/ic/tmiscs.nim` to ensure
required symbols are available for tests.


I tried to improve `resolveFile`, which is harder because either we need
to add `lib/std` to search path and all of other nested directory to
`--path` in `config/nim.cfg`. So I choose toi reuse `findModule` for
imports
2026-04-21 16:38:33 +02:00
Bojun Chai
de3d61f15b Fix invalid Mac OS X minimum version in README (#25758)
**Repo:** nim-lang/Nim ( 16000)
**Type:** docs
**Files changed:** 1
**Lines:** +1/-1

## What
Correct the supported platform table in the top-level README by changing
the Mac OS X minimum version from `10.04` to `10.4`.

## Why
`10.04` is not a valid Mac OS X release number, so the existing text is
misleading for anyone reading the build and platform support guidance.
Fixing it keeps the README accurate without changing project behavior or
widening scope.

## Testing
Verified the README diff locally and confirmed the corrected `Mac OS X
(10.4 or greater)` entry appears in `readme.md`. No code or test suite
changes were needed for this docs-only patch.

## Risk
Low / documentation-only change with no runtime impact.

Co-authored-by: Bojun Chai <bojunchai@microsoft.com>
2026-04-21 08:50:13 +08:00
Tomohiro
ba4e12fb65 fixes #25753 (#25754) 2026-04-20 20:13:06 +02:00
Andreas Rumpf
f236e6a210 fixes #25695 (#25756) 2026-04-20 09:17:12 +02:00
Tomohiro
317bc10824 Makes containsOrIncl*[A](s: var PackedSet[A], key: A) proc faster (#25755)
This PR makes it faster when a number of elements is less than 34
I used following code to compare the speed of `containsOrIncl` proc.
It calls `isRecursiveStructuralType` proc defined in compiler/types.nim
that calls `containsOrIncl` with `IntSet`(= `PackedSet[int]`).
```nim
import std/[tables, monotimes, times, strformat]
import "$nim"/compiler/[astdef, ast, idents, types]

var idgen = IdGenerator(module: 0, symId: 0, typeId: 0, disambTable: initCountTable[PIdent]())

proc newType(kind: TTypeKind; son: sink PType = nil): PType =
  result = newType(kind, idgen, nil, son)

proc genNoRecursPType(len: int): PType =
  assert len > 1
  let intTyp = newType(tyInt)
  result = newType(tyRef, intTyp)
  for i in 0..<(len - 2):
    result = newType(tyRef, result)

proc test =
  var noRecursPType = genNoRecursPType(4)
  assert not isRecursiveStructuralType(noRecursPType)

test()

template measure(label: string; body: untyped): untyped =
  let
    loop = 2000
    sampling = 200
  block:
    var r {.inject.} = false
    var minT = initDuration(hours = 1)
    for i in 0 ..< sampling:
      let start = getMonoTime()
      for j in 0 ..< loop:
        body
      let finish = getMonoTime()
      minT = min(finish - start, minT)
    echo ($r)[0], ' ', label, minT div loop

proc benchNoRecurs(len: int) =
  echo fmt"No recursive: length: {len}"
  var noRecursPType = genNoRecursPType(len)
  measure("IntSet: "):
    r = isRecursiveStructuralType(noRecursPType)

proc bench =
  benchNoRecurs(30)

bench()
```

Output before changing code:
```
f IntSet: 1 microsecond and 262 nanoseconds
```
Output after change:
```
f IntSet: 833 nanoseconds
```

Why this PR make it faster:
```nim
proc containsOrIncl*[A](s: var PackedSet[A], key: A): bool =
  ...
  if s.elems <= s.a.len:
    for i in 0..<s.elems:
      if s.a[i] == ord(key):
        return true
    # `incl` scans `s.a` again
    incl(s, key)
    result = false
```

```nim
proc containsOrIncl*[A](s: var PackedSet[A], key: A): bool =
  ...
  if s.elems <= s.a.len:
    for i in 0..<s.elems:
      if s.a[i] == ord(key):
        return true
    if s.elems < s.a.len:
      # put `key` in `s.a` instead of calling `incl(s, key)`
      s.a[s.elems] = ord(key)
      inc(s.elems)
    else:
      incl(s, key)
    result = false
```
2026-04-20 09:21:46 +08:00
ringabout
5948dbbeed fixes #25718; setLenUnit slow (#25743)
fixes #25718

This pull request optimizes sequence allocation in the Nim standard
library by introducing a way to create uninitialized sequence payloads
for element types that don't require zero-initialization. The changes
allow for more efficient memory allocation when initializing sequences
with types that have no references, avoiding unnecessary zeroing of
memory.

Sequence allocation and initialization improvements:

* Added the `newSeqUninitRaw` procedure to create sequence payloads with
a specified length without forcing zero-initialization for element types
marked as `ntfNoRefs`. (`lib/system/sysstr.nim`,
[lib/system/sysstr.nimR277-R292](diffhunk://#diff-bcaa1967f436ad03877f353823c08a8b4a719fe387629d33aab4bddf16534b5eR277-R292))
* Modified the `extendCapacityRaw` procedure and the `setLengthSeqImpl`
template to use `newSeqUninitRaw` when zero-initialization is not
required, controlled by the `doInit` static parameter.
(`lib/system/sysstr.nim`,
[[1]](diffhunk://#diff-bcaa1967f436ad03877f353823c08a8b4a719fe387629d33aab4bddf16534b5eR277-R292)
[[2]](diffhunk://#diff-bcaa1967f436ad03877f353823c08a8b4a719fe387629d33aab4bddf16534b5eL316-R335)
2026-04-19 20:12:01 +02:00
ringabout
98131a9fa1 fixes #25751; JS backend crashes when returning Option[T] with custom =destroy (#25752)
fixes #25751

This pull request improves the JavaScript backend code generation and
expands test coverage, particularly around temporary and loop variables,
as well as object destruction behavior. The main changes include
updating the code generator to handle more symbol kinds and adding tests
to ensure proper destruction and option handling.

**JavaScript code generation improvements:**

* Updated `genSymAddr` in `compiler/jsgen.nim` to support additional
symbol kinds, specifically `skTemp` and `skForVar`, ensuring correct
address generation for temporaries and loop variables.

**Test suite enhancements:**

* Added tests in `tests/js/test2.nim` to verify correct behavior of
option types, object destruction (`=destroy`), and to check for
backend-specific crashes. This includes printing results of
option-returning functions and confirming destruction messages.
* Updated expected output in `tests/js/test2.nim` to include results
from new tests and destruction messages, ensuring the test suite
reflects the latest code behavior.
2026-04-18 09:40:55 +02:00
Ryan McConnell
f98578ea35 fix 25667; Generic forward type confusion (#25737)
ref: #25667

drain deferred reification in a loop until there is no more work to do.
Could potentially evaluate the same deferred work more than once.

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2026-04-18 08:52:31 +02:00
dependabot[bot]
e6e00a74a3 Bump actions/github-script from 8 to 9 (#25748)
Bumps [actions/github-script](https://github.com/actions/github-script)
from 8 to 9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/github-script/releases">actions/github-script's
releases</a>.</em></p>
<blockquote>
<h2>v9.0.0</h2>
<p><strong>New features:</strong></p>
<ul>
<li><strong><code>getOctokit</code> factory function</strong> —
Available directly in the script context. Create additional
authenticated Octokit clients with different tokens for multi-token
workflows, GitHub App tokens, and cross-org access. See <a
href="https://github.com/actions/github-script#creating-additional-clients-with-getoctokit">Creating
additional clients with <code>getOctokit</code></a> for details and
examples.</li>
<li><strong>Orchestration ID in user-agent</strong> — The
<code>ACTIONS_ORCHESTRATION_ID</code> environment variable is
automatically appended to the user-agent string for request
tracing.</li>
</ul>
<p><strong>Breaking changes:</strong></p>
<ul>
<li><strong><code>require('@actions/github')</code> no longer works in
scripts.</strong> The upgrade to <code>@actions/github</code> v9
(ESM-only) means <code>require('@actions/github')</code> will fail at
runtime. If you previously used patterns like <code>const { getOctokit }
= require('@actions/github')</code> to create secondary clients, use the
new injected <code>getOctokit</code> function instead — it's available
directly in the script context with no imports needed.</li>
<li><code>getOctokit</code> is now an injected function parameter.
Scripts that declare <code>const getOctokit = ...</code> or <code>let
getOctokit = ...</code> will get a <code>SyntaxError</code> because
JavaScript does not allow <code>const</code>/<code>let</code>
redeclaration of function parameters. Use the injected
<code>getOctokit</code> directly, or use <code>var getOctokit =
...</code> if you need to redeclare it.</li>
<li>If your script accesses other <code>@actions/github</code> internals
beyond the standard <code>github</code>/<code>octokit</code> client, you
may need to update those references for v9 compatibility.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Add ACTIONS_ORCHESTRATION_ID to user-agent string by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/695">actions/github-script#695</a></li>
<li>ci: use deployment: false for integration test environments by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/712">actions/github-script#712</a></li>
<li>feat!: add getOctokit to script context, upgrade
<code>@​actions/github</code> v9, <code>@​octokit/core</code> v7, and
related packages by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/700">actions/github-script#700</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/695">actions/github-script#695</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v8.0.0...v9.0.0">https://github.com/actions/github-script/compare/v8.0.0...v9.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3a2844b7e9"><code>3a2844b</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/700">#700</a>
from actions/salmanmkc/expose-getoctokit + prepare re...</li>
<li><a
href="ca10bbdd1a"><code>ca10bbd</code></a>
fix: use <code>@​octokit/core/</code>types import for v7
compatibility</li>
<li><a
href="86e48e20ac"><code>86e48e2</code></a>
merge: incorporate main branch changes</li>
<li><a
href="c1084728b5"><code>c108472</code></a>
chore: rebuild dist for v9 upgrade and getOctokit factory</li>
<li><a
href="afff112e4f"><code>afff112</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/712">#712</a>
from actions/salmanmkc/deployment-false + fix user-ag...</li>
<li><a
href="ff8117e5b7"><code>ff8117e</code></a>
ci: fix user-agent test to handle orchestration ID</li>
<li><a
href="81c6b78760"><code>81c6b78</code></a>
ci: use deployment: false to suppress deployment noise from integration
tests</li>
<li><a
href="3953caf885"><code>3953caf</code></a>
docs: update README examples from <a
href="https://github.com/v8"><code>@​v8</code></a> to <a
href="https://github.com/v9"><code>@​v9</code></a>, add getOctokit docs
and v9 brea...</li>
<li><a
href="c17d55b90d"><code>c17d55b</code></a>
ci: add getOctokit integration test job</li>
<li><a
href="a047196d9a"><code>a047196</code></a>
test: add getOctokit integration tests via callAsyncFunction</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/github-script/compare/v8...v9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=8&new-version=9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 19:14:36 +08:00
ringabout
c22819ef17 fixes #25732; semStaticExpr and semStaticStmt to handle errors (#25742)
fix #25732
2026-04-17 10:00:00 +02:00
ringabout
2b2872928b fixes #25469; Conversion from distinct in for forces a copy of underlying instance (#25746)
fixes #25469

This pull request introduces an important fix to argument handling in
the compiler's transformation logic and adds a new test to verify
correct behavior with distinct types and ARC memory management.

### Compiler transformation improvements

* Updated `putArgInto` in `compiler/transf.nim` to handle
`nkHiddenStdConv`, `nkHiddenSubConv`, and `nkConv` nodes more
accurately. Now, if the types match (ignoring distinctness and shallow
range differences), the argument is recursively processed; otherwise, it
falls back to a fast assignment. This prevents incorrect assignments
when dealing with type conversions and distinct types.

### Testing for distinct types and ARC

* Added a new test `tdistinct_for_nodup.nim` to ensure correct iteration
and memory management for distinct sequences of large arrays under ARC.
The test checks that the sequence length remains unchanged during
iteration, helping catch regressions related to ARC and distinct types.
2026-04-17 09:59:22 +02:00
Andreas Rumpf
b4d4028afa fixes whitespace related endless loop in renderer.nim (#25750) 2026-04-16 09:44:58 +02:00
Sai Asish Y
3eb4a60b6b ccgstmts: fix 'occured' -> 'occurred' typo in emitted C++ exception comment (#25749)
Inline C++ comment emitted by `compiler/ccgstmts.nim:1168` into
generated code read `C++ exception occured, not under Nim's control`.
Doc-only change in the emitted source.

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
Co-authored-by: SAY-5 <SAY-5@users.noreply.github.com>
2026-04-16 12:29:09 +08:00
ringabout
7b73537131 fixes #25735; sso C++: nimToCStringConv (#25745)
fixes #25735

This pull request updates how string-to-C-string conversions are handled
when the `nimsso` configuration flag is enabled, and adds a new system
test to validate the behavior. The main changes focus on switching from
using `addrLoc` to `byRefLoc` for argument preparation, which likely
improves correctness or compatibility with the `nimsso` mode.

**Code generation improvements for `nimsso` mode:**

* In both `compiler/ccgcalls.nim` (`genArgStringToCString`) and
`compiler/ccgexprs.nim` (`convStrToCStr`), replaced the use of `addrLoc`
with `byRefLoc` when preparing arguments for string-to-C-string
conversions under the `nimsso` configuration flag. This change ensures
that references are handled appropriately according to the requirements
of `nimsso`.
[[1]](diffhunk://#diff-42181cc6f4202af843e7835ea514df2efe85e4faae3bc797a39a0c422547b558L373-R373)
[[2]](diffhunk://#diff-4509107d295d7d32b1887c8993cd0f56113ae60f36113e7d8778646dabd92ebcL2739-R2739)

**Testing:**

* Added a new system test `tests/system/tnimsso.nim` that runs with the
`-d:nimsso` flag on both C and C++ targets, checking that
string-to-C-string conversion works as expected in `nimsso` mode.
2026-04-15 14:57:57 +02:00
Andreas Rumpf
5b1a05e282 fixes #18095 (#25744) 2026-04-14 19:58:44 +02:00
Zoom
4dbc382906 Feat: stdlib: adds system.string.setLenUninit (#24836)
Adds `system.setLenUninit` for the `string` type. Allows setting length
without initializing new memory on growth.

- Required for a follow-up to #15951
- Accompanies #22767 (ref #19727) but for strings
- Expands `stdlib/tstring` with tests for `setLen` and `setLenUninit`

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2026-04-14 17:24:26 +02:00
Andreas Rumpf
e81f5b5890 Revert "only generate called hook for explicit or generated destructor calls [backport]" (#25741)
Reverts nim-lang/Nim#25729
2026-04-13 18:02:53 +08:00
lit
cf3c28c223 fixes #25738; std/parseopt: - causes IndexDefect (#25739) 2026-04-12 12:32:25 +02:00
Ryan McConnell
242f761627 RE: fix #25627 (#25736)
Follow up PR to #25700

@demotomohiro 

This doesn't seem to mirror your suggested approach completely. I still
went with a recursive walk. Could probably add some kind of "clean
types" and "dirty types" cache through this to minimize the recursions,
but that seems like a little much.
2026-04-12 08:56:31 +02:00
metagn
a35614e539 make explicit copy and hook calls keep their symbol (#25731)
fixes #25730

As mentioned in the issue this results in less optimized output, it
always generates the explicitly called hook as a proc rather than an
inline assignment. But maybe this is a reasonable trade since it only
happens on explicit `=sink`/`=copy` calls.

Any way to optimize it requires detecting either the type or the found
hook as a trivial assignment. I am not sure how to do these, the hook
isn't like destructors that propagate empty statements in
`liftdestructors` (which is what `isTrivial` checks for), it needs to
propagate simple assignments instead. And there is no logic for the
type, `tfHasAsgn` is misleading since it only checks if the destructor
is trivial, because there is no check for a trivial assignment.

Did not mark as backported but the only issue I can think of is the
performance issue above, otherwise it would be more correct if anything.
2026-04-12 07:05:48 +02:00
metagn
fb02e9831d only generate called hook for explicit or generated destructor calls [backport] (#25729)
fixes #25727, regression from #24627 which was backported to 2.2.2 and
2.0.16

Instead of calling `createTypeBoundOps` for explicit hook calls and when
generating default hooks, only the called destructor is generated at a
time. This allows defining more than 1 hook for recursive types.

`=sink` for `useSeqOrStrOp` and also `atomicRefOp` always need a
`=destroy` hook generated so that is also generated separately. There
might be more that I missed, only the atomicRefOp one failed `trtree` in
CI, and it was just from a compiler assert that got triggered, otherwise
it would still have functioned.
2026-04-12 07:05:11 +02:00
ringabout
6353c4e5b0 fixes #25724; Invalid C code generation with iterator/nimvm (#25728)
fixes #25724

This pull request introduces a small but important fix in the compiler
and adds a new test case related to iterators. The main change in the
compiler ensures that lambda-like constructs are handled consistently
with other procedure definitions, while the new test in the suite covers
a previously untested scenario.

**Compiler improvements:**
* Updated `introduceNewLocalVars` in `compiler/transf.nim` to handle all
`nkLambdaKinds` in addition to `nkProcDef`, `nkFuncDef`, `nkMethodDef`,
and `nkConverterDef`, ensuring consistent transformation of all
lambda-like constructs.

**Testing:**
* Added a block to `tests/iter/titer_issues.nim` to test iterator
behavior in both compile-time and run-time contexts, addressing bug
#25724.
2026-04-10 15:57:26 +02:00
ringabout
e39272eaa8 fixes #25637; nim ic with destructors (#25723)
fixes #25637

This pull request refactors the way the `sfInjectDestructors` flag is
set on symbols during lambda lifting in the Nim compiler. The main
change is the introduction of a helper procedure to encapsulate the
logic for marking symbols that require destructor injection, improving
code clarity and maintainability.

Refactoring and code quality improvements:

* Introduced the `markInjectDestructors` procedure to encapsulate the
logic for marking a symbol with the `sfInjectDestructors` flag, ensuring
that `backendEnsureMutable` is always called before modifying the
symbol's flags.
* Replaced direct flag manipulation (`owner.incl sfInjectDestructors`
and `prc.incl sfInjectDestructors`) with calls to the new
`markInjectDestructors` procedure in multiple locations, including
`makeClosure`, `createTypeBoundOpsLL`, and `rawClosureCreation`.
[[1]](diffhunk://#diff-19193904ba011a2bcc1e1a9768a7eb57cac57a274cad73d388149776ec2901e6L231-R235)
[[2]](diffhunk://#diff-19193904ba011a2bcc1e1a9768a7eb57cac57a274cad73d388149776ec2901e6L243-R247)
[[3]](diffhunk://#diff-19193904ba011a2bcc1e1a9768a7eb57cac57a274cad73d388149776ec2901e6L639-R643)
2026-04-10 15:29:20 +02:00
Ryan McConnell
2501e23d81 fixes #25290; tempalte overload scope dupe (#25308)
#25290
drafted bc if this passes full CI I am going to try and remove that
weird stuff in `pickBestCandidate`
2026-04-09 20:44:35 +02:00
ringabout
188aa1714e fixes #25719; optimizes setLenSeqCall for orc (#25721)
fixes #25719

This pull request updates the logic for resizing sequences during
certain copy operations in the `compiler/liftdestructors.nim` file. The
main improvement is that the code now distinguishes between regular and
uninitialized resizing based on whether the sequence's element type
supports bulk memory copying, which can lead to more efficient code
generation.

**Improvements to sequence resizing and copying logic:**

* Modified `setLenSeqCall` to accept a `noinit` parameter, allowing it
to choose between `setLen` and `setLenUninit` operations, and to select
the appropriate magic for each case.
* Updated `fillSeqOp` to determine if bulk memory copy is supported and,
if so, call `setLenSeqCall` with `noinit = true` and perform a bulk
copy; otherwise, it defaults to element-wise copying. This logic is now
applied in both relevant locations in the function.
[[1]](diffhunk://#diff-456118dde9a4e21f1b351fd72504d62fc16e9c30354dbb9a3efcb95a29067863L646-R650)
[[2]](diffhunk://#diff-456118dde9a4e21f1b351fd72504d62fc16e9c30354dbb9a3efcb95a29067863L661-R666)
2026-04-09 11:11:06 +02:00
Ryan McConnell
fa6b754dbc fix #25627 (#25700)
@demotomohiro this was caused by your PR please review
#25627
2026-04-09 11:09:34 +02:00
ringabout
9a2b0dd045 fixes #25697; {.borrow.} on iterator for distinct seq triggers internal error (#25709)
fixes #25697

This pull request improves the handling of borrowed routines in the
compiler transformation phase, making the code more robust and
maintainable. The main change is the introduction of a helper function
to properly resolve borrowed routine symbols, which is then used in
multiple places to ensure correct symbol resolution. Additionally, a new
test case is added to cover a previously reported bug related to
borrowed iterators on distinct types.

**Compiler improvements:**

* Added `resolveBorrowedRoutineSym` helper function to follow borrow
aliases and retrieve the underlying implementation symbol for borrowed
routines. This centralizes and clarifies the logic for resolving
borrowed symbols.
* Updated `transformSymAux` and `transformFor` to use the new helper
function, replacing duplicated logic and improving correctness when
handling borrowed routines.
[[1]](diffhunk://#diff-c7b80f51fb685eb22c5b56ee2f320d6c708706f3ae7293478ecd104a2b5b8096L139-R154)
[[2]](diffhunk://#diff-c7b80f51fb685eb22c5b56ee2f320d6c708706f3ae7293478ecd104a2b5b8096L788-R795)

**Testing:**

* Added a test case for bug #25697 to `tests/distinct/tborrow.nim`,
ensuring that iteration over a distinct type with a borrowed iterator
works as expected.
2026-04-09 11:08:03 +02:00
ringabout
c8e6b059a4 optimizes setLen for orc; disabling overflow checks (#25722)
ref https://github.com/nim-lang/Nim/issues/25695
ref https://github.com/nim-lang/Nim/pull/25715

This pull request introduces a minor but important change to the
`setLen` procedure in `lib/system/seqs_v2.nim`. The main update is the
temporary disabling of overflow checks during the initialization loop
when extending the sequence length, which can improve performance and
avoid unnecessary checks during this operation.

Memory and performance improvement:

* Disabled overflow checks for the loop that initializes new elements to
their default value when increasing the length of a sequence in
`setLen`, by wrapping the loop with `{.push overflowChecks: off.}` and
`{.pop.}`.
2026-04-09 11:07:04 +02:00
103 changed files with 2376 additions and 317 deletions

View File

@@ -60,7 +60,7 @@ jobs:
run: nim c -r -d:release ci/action.nim
- name: 'Comment'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');

View File

@@ -35,6 +35,10 @@ errors.
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
- Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
## Standard library additions and changes
[//]: # "Additions:"
@@ -60,17 +64,26 @@ errors.
- `copyDirWithPermissions` to recursively preserve attributes
- `system.setLenUninit` now supports refc, JS and VM backends.
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
Modes include `Nim` (default, fully compatible) and two new experimental modes:
`Lax` and `Gnu` for different option parsing behaviors.
- `std/nre2` is added to replace deprecated NRE.
[//]: # "Changes:"
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
Use https://github.com/nitely/nim-regex or `std/nre2`.
See: https://github.com/nim-lang/Nim/issues/23668.
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
works without single-quoting.
## Language changes

View File

@@ -230,11 +230,11 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
of tyString, tySequence:
let atyp = skipTypes(a.t, abstractInst)
if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.isDefined("nimsso") and
if p.config.usesSso() and
skipTypes(a.t, abstractVar + abstractInst).kind == tyString:
let strPtr = if atyp.kind in {tyVar} and not compileToCpp(p.module): ra
else: addrLoc(p.config, a)
@@ -296,11 +296,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
of tyString, tySequence:
let ntyp = skipTypes(n.typ, abstractInst)
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.isDefined("nimsso") and
if p.config.usesSso() and
skipTypes(n.typ, abstractVar + abstractInst).kind == tyString:
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
let ra = a.rdLoc
@@ -335,7 +335,7 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
let ra = a.rdLoc
var t = TLoc(snippet: cDeref(ra))
let lt = lenExpr(p, t)
if p.config.isDefined("nimsso"):
if p.config.usesSso():
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
result.addArgumentSeparator()
result.add(cCall(cgsymValue(p.module, "nimStrLen"), t.snippet))
@@ -370,7 +370,7 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
var a = initLocExpr(p, n[0])
let tmp = withTmpIfNeeded(p, a, needsTmp)
let ra = if p.config.isDefined("nimsso"): addrLoc(p.config, tmp) else: tmp.rdLoc
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =

View File

@@ -322,7 +322,7 @@ proc genOpenArrayConv(p: BProc; d: TLoc; a: TLoc; flags: TAssignmentFlags) =
bra)
let rd = d.rdLoc
let la = lenExpr(p, a)
if p.config.isDefined("nimsso"):
if p.config.usesSso():
let bra = byRefLoc(p, a)
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
cCall(cgsymValue(p.module, "nimStrData"), bra))
@@ -963,7 +963,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
proc cowBracket(p: BProc; n: PNode) =
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and
not p.config.isDefined("nimsso"):
not p.config.usesSso():
let strCandidate = n[0]
if strCandidate.typ.skipTypes(abstractInst).kind == tyString:
var a: TLoc = initLocExpr(p, strCandidate)
@@ -974,9 +974,7 @@ proc cow(p: BProc; n: PNode) {.inline.} =
if n.kind == nkHiddenAddr: cowBracket(p, n[0])
template ignoreConv(e: PNode): bool =
let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink})
let srcType = e[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink})
sameBackendTypePickyAliases(destType, srcType)
sameBackendTypePickyAliases(e.typ, e[1].typ)
proc genAddr(p: BProc, e: PNode, d: var TLoc) =
# careful 'addr(myptrToArray)' needs to get the ampersand:
@@ -989,7 +987,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) =
# bug #19497
d.lode = e
else:
let ssoStrSub = p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
let ssoStrSub = p.config.usesSso() and e[0].kind == nkBracketExpr and
e[0][0].typ.skipTypes(abstractVar).kind == tyString
var a: TLoc = initLocExpr(p, e[0], if ssoStrSub: {lfEnforceDeref, lfPrepareForMutation} else: {})
if e[0].kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e[0]):
@@ -1318,7 +1316,7 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
a.snippet = cDeref(a.snippet)
if p.config.isDefined("nimsso") and ty.kind == tyString:
if p.config.usesSso() and ty.kind == tyString:
let bra = byRefLoc(p, a)
if lfPrepareForMutation in d.flags:
# Use nimStrAtMutV3 to get a mutable reference (char*) to the element.
@@ -1889,10 +1887,17 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
var t = e.typ.skipTypes(abstractInstOwned)
let isRef = t.kind == tyRef
# check if we need to construct the object in a temporary
# check if we need to construct the object in a temporary.
# A temp is needed when:
# - the constructor produces a ref (isRef)
# - the destination is not a writable location (d.k == locNone)
# - the constructed type differs from the destination type (subtype
# assignments need the genAssignment path for ObjectAssignmentDefect)
# - the constructor's field values may alias the destination (isPartOf)
var useTemp =
isRef or
(d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or
d.k == locNone or
(d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or
(isPartOf(d.lode, e) != arNo)
var tmp: TLoc = default(TLoc)
@@ -2143,7 +2148,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
putIntoDest(p, b, e, ra & cArgumentSeparator & ra & "Len_0", a.storage)
of tyString, tySequence:
let la = lenExpr(p, a)
if p.config.isDefined("nimsso") and
if p.config.usesSso() and
skipTypes(a.t, abstractVarRange).kind == tyString:
let bra = byRefLoc(p, a)
putIntoDest(p, b, e,
@@ -2736,7 +2741,7 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) =
proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc = initLocExpr(p, n[0])
let arg = if p.config.isDefined("nimsso"): addrLoc(p.config, a) else: rdLoc(a)
let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a)
putIntoDest(p, d, n,
cgCall(p, "nimToCStringConv", arg),
a.storage)
@@ -2809,13 +2814,13 @@ proc genWasMoved(p: BProc; n: PNode) =
# [addrLoc(p.config, a), getTypeDesc(p.module, a.t)])
proc genMove(p: BProc; n: PNode; d: var TLoc) =
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
if n.len == 4:
# generated by liftdestructors:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
var src: TLoc = initLocExpr(p, n[2])
let destVal = rdLoc(a)
let srcVal = rdLoc(src)
if p.config.isDefined("nimsso") and
if p.config.usesSso() and
n[1].typ.skipTypes(abstractVar).kind == tyString:
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
genStmts(p, n[3])
@@ -2831,29 +2836,16 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
else:
if d.k == locNone: d = getTemp(p, n.typ)
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
genAssignment(p, d, a, {})
var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved)
if op == nil:
if op == nil or sfOverridden notin op.flags:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
resetLoc(p, a)
else:
var b = initLocExpr(p, newSymNode(op))
case skipTypes(a.t, abstractVar+{tyStatic}).kind
of tyOpenArray, tyVarargs: # todo fixme generated `wasMoved` hooks for
# openarrays, but it probably shouldn't?
let ra = rdLoc(a)
var s: string
if reifiedOpenArray(a.lode):
if a.t.kind in {tyVar, tyLent}:
s = derefField(ra, "Field0") & cArgumentSeparator & derefField(ra, "Field1")
else:
s = dotField(ra, "Field0") & cArgumentSeparator & dotField(ra, "Field1")
else:
s = ra & cArgumentSeparator & ra & "Len_0"
p.s(cpsStmts).addCallStmt(rdLoc(b), s)
else:
let val = if p.module.compileToCpp: rdLoc(a) else: byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(rdLoc(b), val)
n[1] = makeAddr(n[1], p.module.idgen)
genCall(p, n, d)
else:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
resetLoc(p, a)
@@ -2864,7 +2856,7 @@ proc genDestroy(p: BProc; n: PNode) =
case t.kind
of tyString:
var a: TLoc = initLocExpr(p, arg)
if p.config.isDefined("nimsso"):
if p.config.usesSso():
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
else:
@@ -4236,7 +4228,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
genConstObjConstr(p, n, isConst, result)
of tyString, tyCstring:
if optSeqDestructors in p.config.globalOptions and n.kind != nkNilLit and ty == tyString:
if p.config.isDefined("nimsso"):
if p.config.usesSso():
genStringLiteralV3Const(p.module, n, isConst, result)
else:
genStringLiteralV2Const(p.module, n, isConst, result)

View File

@@ -22,7 +22,7 @@ template detectVersion(field, corename) =
result = 1
proc detectStrVersion(m: BModule): int =
if m.g.config.isDefined("nimsso") and
if m.g.config.usesSso() and
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 3
else:

View File

@@ -230,7 +230,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt
# Called by return and break stmts.
# Deals with issues faced when jumping out of try/except/finally stmts.
var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
var stack = newSeq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]](0)
inc p.withinBlockLeaveActions
for i in 1..howManyTrys:
@@ -341,9 +341,9 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
call[i][0]
else:
call[i]
if param.kind != nkBracketExpr or param.typ.kind in
if not param.typ.isCompileTimeOnly and (param.kind != nkBracketExpr or param.typ.kind in
{tyRef, tyPtr, tyUncheckedArray, tyArray, tyOpenArray,
tyVarargs, tySequence, tyString, tyCstring, tyTuple}:
tyVarargs, tySequence, tyString, tyCstring, tyTuple}):
let tempLoc = initLocExprSingleUse(p, param)
didGenTemp = didGenTemp or tempLoc.k == locTemp
genOtherArg(p, call, i, typ, res, argBuilder)
@@ -836,12 +836,26 @@ proc raiseExitCleanup(p: BProc, destroy: string) =
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
proc finallyActions(p: BProc) =
if p.config.exc != excGoto and p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
# if the current try stmt have a finally block,
# we must execute it before reraising
let finallyBlock = p.nestedTryStmts[^1].fin
if finallyBlock != nil:
genSimpleBlock(p, finallyBlock[0])
if p.config.exc != excGoto:
# Walk past compiler-injected `nkHiddenTryStmt` wrappers (e.g. ARC's
# destructor try/finally that wraps `except T as e:` bodies) to reach
# the user's actual try. We must NOT walk past a real user try whose
# body we are currently in, because a raise from there will be caught
# by that try's own except branches rather than escaping outward.
#
# If after skipping wrappers the next entry is a user try in its
# except branch (inExcept=true), inline its finally body before the
# raise propagates — without this, the C++ sibling-catch rule would
# cause the user's catch(...)/finally pair to be bypassed and the
# finally would be silently dropped.
for i in countdown(p.nestedTryStmts.high, 0):
if p.nestedTryStmts[i].isHidden:
continue
if p.nestedTryStmts[i].inExcept:
let finallyBlock = p.nestedTryStmts[i].fin
if finallyBlock != nil:
genSimpleBlock(p, finallyBlock[0])
return
proc raiseInstr(p: BProc; result: var Builder) =
if p.config.exc == excGoto:
@@ -1165,7 +1179,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
throw;
}
} catch(...) {
// C++ exception occured, not under Nim's control.
// C++ exception occurred, not under Nim's control.
}
{
/* finally: */
@@ -1185,7 +1199,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, 0.Natural))
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
if t.kind == nkHiddenTryStmt:
lineCg(p, cpsStmts, "try {$n", [])
@@ -1371,7 +1385,7 @@ proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
genLineDir(p, t)
cgsym(p.module, "popCurrentExceptionEx")
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, 0.Natural))
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
startBlockWith(p):
p.s(cpsStmts).add("try {\n")
expr(p, t[0], d)
@@ -1450,7 +1464,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
let lab = p.labels
let hasExcept = t[1].kind == nkExceptBranch
if hasExcept: inc p.withinTryWithExcept
p.nestedTryStmts.add((fin, false, Natural lab))
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab))
p.flags.incl nimErrorFlagAccessed
@@ -1656,7 +1670,7 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
expr(p, t[0], d)
var quirkyIf = default(IfBuilder)
var quirkyScope = default(ScopeBuilder)
@@ -1940,7 +1954,7 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
elif optFieldCheck in p.options and isDiscriminantField(e[0]):
genLineDir(p, e)
asgnFieldDiscriminant(p, e)
elif p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
elif p.config.usesSso() and e[0].kind == nkBracketExpr and
e[0][0].typ.skipTypes(abstractVar).kind == tyString:
# nimsso: s[i] = c → nimStrPutV3(&s, i, c) (handles COW internally)
genLineDir(p, e)

View File

@@ -389,7 +389,7 @@ proc lenField(p: BProc, val: Rope): Rope {.inline.} =
proc lenExpr(p: BProc; a: TLoc): Rope =
if optSeqDestructors in p.config.globalOptions:
if p.config.isDefined("nimsso") and a.lode != nil and a.t != nil and
if p.config.usesSso() and a.lode != nil and a.t != nil and
a.t.skipTypes(abstractInst).kind == tyString:
result = cCall(cgsymValue(p.module, "nimStrLen"), rdLoc(a))
else:
@@ -534,7 +534,7 @@ proc resetLoc(p: BProc, loc: var TLoc) =
let atyp = skipTypes(loc.t, abstractInst)
let rl = rdLoc(loc)
if typ.kind == tyString and p.config.isDefined("nimsso"):
if typ.kind == tyString and p.config.usesSso():
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
if atyp.kind in {tyVar, tyLent}:
p.s(cpsStmts).addAssignment(derefField(rl, "bytes"), cIntValue(0))
@@ -592,7 +592,7 @@ proc constructLoc(p: BProc, loc: var TLoc, isTemp = false) =
let typ = loc.t
if optSeqDestructors in p.config.globalOptions and skipTypes(typ, abstractInst + {tyStatic}).kind in {tyString, tySequence}:
let rl = rdLoc(loc)
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.isDefined("nimsso"):
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.usesSso():
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
p.s(cpsStmts).addFieldAssignment(rl, "bytes", cIntValue(0))
p.s(cpsStmts).addFieldAssignment(rl, "more", NimNil)

View File

@@ -75,10 +75,13 @@ type
flags*: set[TCProcFlag]
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
currLineInfo*: TLineInfo # AST codegen will make this superfluous
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
# in how many nested try statements we are
# (the vars must be volatile then)
# bool is true when are in the except part of a try block
# `inExcept` is true when we are in the except part of a try block.
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
# finallyActions walks past such wrappers to reach the user's try.
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
# using return in finally statements
labels*: Natural # for generating unique labels in the C proc

View File

@@ -250,6 +250,7 @@ const
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
template warningOptionNoop(switch: string) =
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
@@ -306,6 +307,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
else:
result = false
localError(conf, info, errInvalidExceptionSystem % arg)
of "strings":
case arg.normalize
of "default": result = conf.selectedStrings == stringDefault
of "sso": result = conf.selectedStrings == stringSso
else:
result = false
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
of "experimental":
try:
result = conf.features.contains parseEnum[Feature](arg)
@@ -750,6 +758,17 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
processMemoryManagementOption(switch, arg, pass, info, conf)
of "mm":
processMemoryManagementOption(switch, arg, pass, info, conf)
of "strings":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:
case arg.normalize
of "default":
conf.selectedStrings = stringDefault
of "sso":
conf.selectedStrings = stringSso
defineSymbol(conf.symbols, "nimsso")
else:
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
of "warnings", "w":
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
@@ -911,7 +930,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitImports.add m
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:

View File

@@ -10,10 +10,10 @@
## Generate a .build.nif file for nifmake from a Nim project.
## This enables incremental and parallel compilation using the `m` switch.
import std / [os, tables, sets, times, osproc, strutils]
import std / [os, tables, sets, times, osproc]
import options, msgs, lineinfos, pathutils
import "../dist/nimony/src/lib" / [nifstreams, nifcursors, bitabs, nifreader, nifbuilder]
import "../dist/nimony/src/lib" / [nifstreams, bitabs, nifreader, nifbuilder]
import "../dist/nimony/src/gear2" / modnames
type
@@ -79,22 +79,19 @@ proc runNifler(c: DepContext; nimFile: string): bool =
let exitCode = execShellCmd(cmd)
result = exitCode == 0
proc resolveFile(c: DepContext; origin, toResolve: string): string =
## Resolve an import path relative to origin file
# Handle std/ prefix
var path = toResolve
if path.startsWith("std/"):
path = path.substr(4)
proc resolveImport(c: DepContext; origin, toResolve: string): string =
## Resolve an import path using the compiler's normal module lookup rules.
result = findModule(c.config, toResolve, origin).string
# Try relative to origin first
proc resolveInclude(c: DepContext; origin, toResolve: string): string =
## Resolve an include path relative to the including file or the search paths.
let originDir = parentDir(origin)
result = originDir / path.addFileExt("nim")
result = originDir / toResolve.addFileExt("nim")
if fileExists(result):
return result
# Try search paths
for searchPath in c.config.searchPaths:
result = searchPath.string / path.addFileExt("nim")
result = searchPath.string / toResolve.addFileExt("nim")
if fileExists(result):
return result
@@ -103,7 +100,7 @@ proc resolveFile(c: DepContext; origin, toResolve: string): string =
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node)
proc processInclude(c: var DepContext; includePath: string; current: Node) =
let resolved = resolveFile(c, current.files[current.files.len - 1].nimFile, includePath)
let resolved = resolveInclude(c, current.files[current.files.len - 1].nimFile, includePath)
if resolved.len == 0 or not fileExists(resolved):
return
@@ -118,7 +115,7 @@ proc processInclude(c: var DepContext; includePath: string; current: Node) =
discard c.includeStack.pop()
proc processImport(c: var DepContext; importPath: string; current: Node) =
let resolved = resolveFile(c, current.files[0].nimFile, importPath)
let resolved = resolveImport(c, current.files[0].nimFile, importPath)
if resolved.len == 0 or not fileExists(resolved):
return
@@ -140,6 +137,171 @@ proc processImport(c: var DepContext; importPath: string; current: Node) =
if existingIdx notin current.deps:
current.deps.add existingIdx
proc skipSubtree(s: var Stream; first: PackedToken) =
## Consume tokens until the ParLe at `first` is balanced. Caller has
## already obtained `first`.
if first.kind != ParLe: return
var depth = 1
while depth > 0:
let t = next(s)
if t.kind == ParLe: inc depth
elif t.kind == ParRi: dec depth
elif t.kind == EofToken: return
proc evalCondExpr(c: DepContext; s: var Stream): bool =
## Read exactly one condition expression from `s` and return its truth
## value. Consumes tokens whether the expression is recognised or not so
## the caller stays in sync. Recognises `defined(IDENT)`, the boolean
## operators `not`/`and`/`or`, and the literals `true`/`false`. Anything
## else (e.g. a call to an arbitrary proc) is treated as `true` — the
## conservative direction, since a false negative here drops a real
## dependency from the build graph.
let t = next(s)
case t.kind
of Ident:
case pool.strings[t.litId]
of "true": result = true
of "false": result = false
else: result = true
of ParLe:
let tag = pool.tags[t.tagId]
case tag
of "call", "cmd", "callstrlit", "infix", "prefix":
# First child is the head (function/operator name).
let head = next(s)
var name = ""
if head.kind == Ident: name = pool.strings[head.litId]
case name
of "defined":
let arg = next(s)
var sym = ""
if arg.kind == Ident: sym = pool.strings[arg.litId]
result = sym.len > 0 and isDefined(c.config, sym)
of "not":
result = not evalCondExpr(c, s)
of "and":
result = evalCondExpr(c, s)
if result: result = evalCondExpr(c, s)
else: skipSubtree(s, next(s))
of "or":
result = evalCondExpr(c, s)
if not result: result = evalCondExpr(c, s)
else: skipSubtree(s, next(s))
else:
result = true
# Drain whatever remains until the matching ParRi.
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
of "not":
result = not evalCondExpr(c, s)
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
of "and":
result = evalCondExpr(c, s)
if result: result = evalCondExpr(c, s)
else: skipSubtree(s, next(s))
# consume closing ParRi
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
of "or":
result = evalCondExpr(c, s)
if not result: result = evalCondExpr(c, s)
else: skipSubtree(s, next(s))
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
else:
skipSubtree(s, t)
result = true
else:
result = true
proc whenMarkerHolds(c: DepContext; s: var Stream): bool =
## Caller has just consumed the `(when` ParLe. Read children until the
## matching `)`, AND-ing each evaluated condition.
result = true
while true:
# peek by reading; if it's ParRi, we're done
let t = next(s)
if t.kind == ParRi: return
if t.kind == EofToken: return
if t.kind == ParLe:
# Re-feed by manually evaluating the subtree starting at `t`.
# evalCondExpr expects to read its own opener, so handle it directly.
let tag = pool.tags[t.tagId]
case tag
of "call", "cmd", "callstrlit", "infix", "prefix":
let head = next(s)
var name = ""
if head.kind == Ident: name = pool.strings[head.litId]
var ok = true
case name
of "defined":
let arg = next(s)
var sym = ""
if arg.kind == Ident: sym = pool.strings[arg.litId]
ok = sym.len > 0 and isDefined(c.config, sym)
of "not":
ok = not evalCondExpr(c, s)
of "and":
ok = evalCondExpr(c, s)
if ok: ok = evalCondExpr(c, s)
of "or":
ok = evalCondExpr(c, s)
if not ok: ok = evalCondExpr(c, s)
else:
ok = true
# finish the subtree
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
if not ok: result = false
of "not", "and", "or":
# Re-emit a synthetic dispatch: rewrap by descending.
var ok = true
case tag
of "not":
ok = not evalCondExpr(c, s)
of "and":
ok = evalCondExpr(c, s)
if ok: ok = evalCondExpr(c, s)
of "or":
ok = evalCondExpr(c, s)
if not ok: ok = evalCondExpr(c, s)
else: discard
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: return
if not ok: result = false
else:
# Unknown — treat as true and skip.
skipSubtree(s, t)
elif t.kind == Ident:
let v = pool.strings[t.litId]
if v == "false": result = false
# else (true / unknown ident): keep result
proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
## Read a .deps.nif file and process imports/includes
let depsPath = c.depsFile(pair)
@@ -161,12 +323,27 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
if t.kind == ParLe:
let tag = pool.tags[t.tagId]
case tag
of "import", "fromimport":
# Read import path
of "import", "fromimport", "include":
# Read first child. May be a `(when COND...)` marker — parse and
# evaluate; if the condition is statically false, skip the import
# entirely. Otherwise advance past the marker and parse the path.
t = next(s)
# Check for "when" marker (conditional import)
if t.kind == Ident and pool.strings[t.litId] == "when":
t = next(s) # skip it, still process the import
var live = true
if t.kind == ParLe and pool.tags[t.tagId] == "when":
# whenMarkerHolds consumes everything up to and including the
# closing `)` of the `(when ...)` subtree.
live = whenMarkerHolds(c, s)
t = next(s)
if not live:
# Drain the rest of this import/include node.
var depth = 1
while depth > 0:
let n = next(s)
if n.kind == ParLe: inc depth
elif n.kind == ParRi: dec depth
elif n.kind == EofToken: break
t = next(s)
continue
# Handle path expression (could be ident, string, or infix like std/foo)
var importPath = ""
if t.kind == Ident:
@@ -184,26 +361,11 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
if t.kind == Ident: # second part (foo)
importPath = importPath & "/" & pool.strings[t.litId]
if importPath.len > 0:
processImport(c, importPath, current)
# Skip to end of import node
var depth = 1
while depth > 0:
t = next(s)
if t.kind == ParLe: inc depth
elif t.kind == ParRi: dec depth
of "include":
# Read include path
t = next(s)
if t.kind == Ident and pool.strings[t.litId] == "when":
t = next(s) # skip conditional marker
var includePath = ""
if t.kind == Ident:
includePath = pool.strings[t.litId]
elif t.kind == StringLit:
includePath = pool.strings[t.litId]
if includePath.len > 0:
processInclude(c, includePath, current)
# Skip to end
if tag == "include":
processInclude(c, importPath, current)
else:
processImport(c, importPath, current)
# Skip to end of node
var depth = 1
while depth > 0:
t = next(s)
@@ -326,10 +488,19 @@ proc generateBuildFile(c: DepContext): string =
let exeFile = changeFileExt(c.nodes[0].files[0].nimFile, ExeExt)
b.addTree "do"
b.addIdent "nim_nifc"
# Input: .nim file (expanded as argument) and .nif file (dependency)
# Input: .nim file (expanded as argument)
b.addTree "input"
b.addStrLit mainNif
b.endTree()
# Also depend on the semmed .nif files of the main module and all its
# dependencies. nifmake's topological sort orders nodes by depth; without
# these inputs the nim_nifc node sits at depth 1 (no recognized inputs)
# alongside the nifler nodes and runs *before* the nim_m steps that
# produce the .nif files it needs to read.
for node in c.nodes:
b.addTree "input"
b.addStrLit c.semmedFile(node.files[0])
b.endTree()
b.addTree "output"
b.addStrLit exeFile
b.endTree()

View File

@@ -13,7 +13,7 @@ import
ast, msgs, options, idents, lookups,
semdata, modulepaths, sigmatch, lineinfos,
modulegraphs, wordrecg
from std/strutils import `%`, startsWith
from std/strutils import `%`, startsWith, replace
from std/sequtils import addUnique
import std/[sets, tables, intsets]
@@ -304,9 +304,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
var prefix = ""
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
let moduleName = getModuleName(c.config, n)
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
message(c.config, n.info, warnStdPrefix, realModule.name.s)
proc suggestMod(n: PNode; s: PSym) =

View File

@@ -1544,7 +1544,7 @@ proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) =
r.res = s.loc.snippet
r.address = ""
r.typ = etyNone
of skVar, skLet, skResult:
of skVar, skLet, skResult, skTemp, skForVar:
r.kind = resExpr
let jsType = mapType(p):
if typ.isNil:

View File

@@ -1349,7 +1349,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
of '-':
if L.buf[L.bufpos+1] in {'0'..'9'} and
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
# x)-23 # binary minus
# ,-23 # unary minus
# \n-78 # unary minus? Yes.

View File

@@ -592,10 +592,12 @@ proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x))
result.add lenCall
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode): PNode =
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode =
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
let name = if noinit: "setLenUninit" else: "setLen"
let magic = if noinit: mSetLengthSeqUninit else: mSetLengthSeq
var op = getSysMagic(c.g, x.info, name, magic)
op = instantiateGeneric(c, op, t, t)
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
@@ -643,8 +645,9 @@ proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind
of attachedDup:
body.add setLenSeqCall(c, t, x, y)
if supportsCopyMem(t.elementType):
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
@@ -658,8 +661,9 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# This is usually more efficient than a destroy/create pair.
# For trivially copyable types, use bulk copyMem instead of element loop.
checkSelfAssignment(c, t, body, x, y)
body.add setLenSeqCall(c, t, x, y)
if supportsCopyMem(t.elementType):
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
@@ -728,7 +732,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedAsgn, attachedDeepCopy, attachedDup:
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
of attachedSink:
if c.g.config.isDefined("nimsso"):
if c.g.config.usesSso():
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
doAssert t.destructor != nil

View File

@@ -12,7 +12,8 @@ define:nimPreviewNonVarDestructor
define:nimPreviewCheckedClose
define:nimPreviewAsmSemSymbol
define:nimPreviewCStringComparisons
define:nimPreviewDuplicateModuleError
#define:nimPreviewDuplicateModuleError
# Incompatible with Nimony's compat2.nim for now
threads:off

View File

@@ -121,6 +121,11 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
initOrcDefines(conf)
if conf.selectedStrings == stringSso and
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
rawMessage(conf, errGenerated,
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
mainCommand(graph)
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
#echo(GC_getStatistics())

View File

@@ -259,6 +259,9 @@ type
## Old transformation for closures in JS backend
noPanicOnExcept
## don't panic on bare except
procParamTypeBackendAliases
## Keep the old proc type compatibility rules that ignore backend
## c type aliases.
SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
@@ -267,6 +270,10 @@ type
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
StringsMode* = enum
stringDefault = "default"
stringSso = "sso"
ExceptionSystem* = enum
excNone, # no exception system selected yet
excSetjmp, # setjmp based exception handling
@@ -366,6 +373,7 @@ type
implicitCmd*: bool # whether some flag triggered an implicit `command`
selectedGC*: TGCMode # the selected GC (+)
exc*: ExceptionSystem
selectedStrings*: StringsMode
hintProcessingDots*: bool # true for dots, false for filenames
verbosity*: int # how verbose the compiler is
numberOfProcessors*: int # number of processors
@@ -698,6 +706,7 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
template compilationCachePresent*(conf: ConfigRef): untyped =
false

View File

@@ -582,6 +582,7 @@ proc put(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) =
inc(g.lineLen, s.len)
proc putComment(g: var TSrcGen, s: string) =
const SpecialWhitespace = {' ', '\t', '\r', '\n', '\0'}
if s.len == 0: return
var i = 0
let hi = s.len - 1
@@ -611,12 +612,12 @@ proc putComment(g: var TSrcGen, s: string) =
# gets too long:
# compute length of the following word:
var j = i
while j <= hi and s[j] > ' ': inc(j)
while j <= hi and s[j] notin SpecialWhitespace: inc(j)
if not isCode and (g.col + (j - i) > MaxLineLen):
put(g, tkComment, com)
optNL(g, ind)
com = "## "
while i <= hi and s[i] > ' ':
while i <= hi and s[i] notin SpecialWhitespace:
com.add(s[i])
inc(i)
put(g, tkComment, com)

View File

@@ -131,7 +131,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
var sym = syms[0].s
let name = sym.name
var scope = syms[0].scope
c.openShadowScope
if allowTypeBoundOps:
for a in 1 ..< n.len:
# for every already typed argument, add type bound ops
@@ -218,6 +218,10 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
scope = syms[nextSymIndex].scope
inc(nextSymIndex)
if best.state == csMatch and best.calleeSym != nil and best.calleeSym.kind in {skTemplate, skMacro}:
c.closeShadowScope
else:
c.mergeShadowScope
proc effectProblem(f, a: PType; result: var string; c: PContext) =
if f.kind == tyProc and a.kind == tyProc:

View File

@@ -180,9 +180,12 @@ type
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
inUncheckedAssignSection*: int
importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
forwardTypeUpdates*: seq[(PType, PNode)]
# types that need to be updated in a type section
# due to containing forward types, and their corresponding nodes
forwardTypeUpdates*: seq[(PSym, PType, PNode)]
# top-level owner, type, and type node for delayed retries inside a
# type section due to containing forward types
forwardFieldUpdates*: seq[(PType, PNode, PType)]
# object/tuple field definitions whose default values mention forward
# types and need delayed const checking
inTypeofContext*: int
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}
@@ -634,6 +637,11 @@ proc renderNotLValue*(n: PNode): string =
elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
proc isSsoStringIndex*(conf: ConfigRef; n: PNode): bool =
result = conf.usesSso() and n.kind == nkBracketExpr and n.len >= 1 and
n[0].typ != nil and
n[0].typ.skipTypes(abstractVar + abstractInst - {tyTypeDesc}).kind == tyString
proc isAssignable(c: PContext, n: PNode): TAssignableResult =
result = parampatterns.isAssignable(c.p.owner, n)
@@ -775,9 +783,17 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
result[0] = newSymNode(op)
analyseIfAddressTakenInCall(c, result, false)
of attachedSink:
result = c.semAsgnOpr(c, n, nkSinkAsgn)
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
of attachedAsgn:
result = c.semAsgnOpr(c, n, nkAsgn)
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
of attachedDeepCopy:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})

View File

@@ -578,7 +578,14 @@ proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode =
if efExplain in flags:
m.diagnostics = @[]
m.diagnosticsEnabled = true
res = typeRel(m, t2, t1) >= isSubtype # isNone
let rel = typeRel(m, t2, t1)
res = rel >= isSubtype # isNone
if res and rel == isEqual and
not compareTypes(t1, t2,
flags = {ExactTypeDescValues,
PickyCAliases,
PickyBackendAliases}):
res = false
# `res = sameType(t1, t2)` would be wrong, e.g. for `int is (int|float)`
result = newIntNode(nkIntLit, ord(res))
@@ -652,6 +659,9 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
result = semExpr(c, result, flags = {efNoUndeclared})
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
template isViewTarget(t: PType): bool =
t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyVar, tyLent}
case n.kind
of nkCurly:
for i in 0..<n.len:
@@ -680,12 +690,15 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
if f == nil:
globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
return
changeType(c, n[i][1], f.typ, check)
if not isViewTarget(f.typ):
changeType(c, n[i][1], f.typ, check)
else:
changeType(c, n[i][1], tup[i], check)
if not isViewTarget(tup[i]):
changeType(c, n[i][1], tup[i], check)
else:
for i in 0..<n.len:
changeType(c, n[i], tup[i], check)
if not isViewTarget(tup[i]):
changeType(c, n[i], tup[i], check)
when false:
var m = n[i]
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
@@ -708,6 +721,7 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
localError(c.config, n.info, "cannot convert '" & n.sym.name.s &
"' to '" & typeNameAndDesc(newType) & "'")
else: discard
n.typ = newType
proc arrayConstrType(c: PContext, n: PNode): PType =
@@ -963,12 +977,15 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
# echo "SUCCESS evaluated at compile time: ", call.renderTree
proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semExprWithType(c, n, expectedType = expectedType)
closeScope(c)
dec c.inStaticContext
if a.findUnresolvedStatic != nil: return a
if a.findUnresolvedStatic != nil or
c.config.errorCounter != oldErrorCount:
return a
result = evalStaticExpr(c.module, c.idgen, c.graph, a, c.p.owner)
if result.isNil:
localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n))

View File

@@ -81,7 +81,8 @@ proc sameInstantiation(a, b: TInstantiation): bool =
if not compareTypes(a.concreteTypes[i], b.concreteTypes[i],
flags = {ExactTypeDescValues,
ExactGcSafety,
PickyCAliases}): return
PickyCAliases,
PickyBackendAliases}): return
result = true
else:
result = false

View File

@@ -248,10 +248,13 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
assert operand.kind == tyTuple, $operand.kind
result = newIntNodeT(toInt128(operand.len), traitCall, c.idgen, c.graph)
of "distinctBase":
var arg = operand.skipTypes({tyGenericInst})
var arg = operand.skipTypes(skippedTypes)
let rec = semConstExpr(c, traitCall[2]).intVal != 0
while arg.kind == tyDistinct:
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
while true:
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
if distinctArg.kind != tyDistinct:
break
arg = distinctArg.base.skipTypes(skippedTypes)
if not rec: break
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
of "rangeBase":
@@ -615,9 +618,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
of mAsgn:
case n[0].sym.name.s
of "=", "=copy":
result = semAsgnOpr(c, n, nkAsgn)
result = replaceHookMagic(c, n, attachedAsgn)
of "=sink":
result = semAsgnOpr(c, n, nkSinkAsgn)
result = replaceHookMagic(c, n, attachedSink)
else:
result = semShallowCopy(c, n, flags)
of mIsPartOf: result = semIsPartOf(c, n, flags)

View File

@@ -486,6 +486,11 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
# we have to watch out, there are also 'owned proc' types that can be used
# multiple times as long as they don't have closures.
result.typ.incl tfHasOwned
if t.kind == tyForward and efDetermineType in flags:
# a forward object type does not error during determine-type analysis;
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
result.typ = t
return result
if t.kind != tyObject:
return localErrorNode(c, result, if t.kind != tyGenericBody:
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))

View File

@@ -809,6 +809,10 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.signatureLen: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}:
let arg = n.skipAddr()
if isSsoStringIndex(tracked.config, arg):
localError(tracked.config, arg.info,
"expression '$1' is immutable, not 'var'" % renderNotLValue(arg))
invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalSym(tracked, n.sym):
makeVolatile(tracked, n.sym)

View File

@@ -1808,15 +1808,35 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
internalAssert c.config, false
proc typeSectionFinalPass(c: PContext, n: PNode) =
for (typ, typeNode) in c.forwardTypeUpdates:
# types that need to be updated due to containing forward types
# and their corresponding type nodes
# for example generic invocations of forward types end up here
var reified = semTypeNode(c, typeNode, nil)
assert reified != nil
assignType(typ, reified)
typ.itemId = reified.itemId # same id
c.forwardTypeUpdates = @[]
# each top level type needs to be processed, each epoch should reify at least one
var remainingOwners = initIntSet()
for (owner, _, _) in c.forwardTypeUpdates:
remainingOwners.incl owner.id
while c.forwardTypeUpdates.len > 0:
let pending = move c.forwardTypeUpdates
var madeProgress = false
for (owner, typ, typeNode) in pending:
# types that need to be updated due to containing forward types
# and their corresponding type nodes
# for example generic invocations of forward types end up here
var reified = semTypeNode(c, typeNode, nil)
assert reified != nil
assignType(typ, reified)
typ.itemId = reified.itemId # same id
if containsForwardType(typ):
c.forwardTypeUpdates.add (owner, typ, typeNode)
elif not remainingOwners.missingOrExcl(owner.id):
madeProgress = true
if not madeProgress:
# can't error here unfortunately
break
for (owner, field, expectedType) in c.forwardFieldUpdates:
semDelayedFieldDefault(c, owner, expectedType, field)
c.forwardFieldUpdates = @[]
for i in 0..<n.len:
var a = n[i]
if a.kind == nkCommentStmt: continue
@@ -2916,13 +2936,15 @@ proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode =
#echo "semStaticStmt"
#writeStackTrace()
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semStmt(c, n[0], {})
closeScope(c)
dec c.inStaticContext
n[0] = a
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
if c.config.errorCounter == oldErrorCount:
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
when false:
# for incremental replays, keep the AST as required for replays:
result = n

View File

@@ -223,7 +223,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
if base.kind notin {tyGenericParam, tyGenericInvocation}:
if base.kind == tyForward:
c.forwardTypeUpdates.add (base, n[1])
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
elif not isOrdinalType(base, allowEnumWithHoles = true):
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
elif lengthOrd(c.config, base) > MaxSetElements:
@@ -318,6 +318,62 @@ proc fitDefaultNode(c: PContext, n: var PNode, expectedType: PType) =
typeAllowedCheck(c, n.info, n.typ, skConst, {taProcContextIsNotMacro, taIsDefaultField})
dec c.inStaticContext
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool
proc containsForwardTypeAux(n: PNode; seen: var IntSet): bool =
result = false
if n.isNil or n.kind in nkLiterals + {nkNilLit, nkEmpty, nkType}:
return
if containsForwardTypeAux(n.typ, seen) or
(n.kind == nkSym and n.sym.typ != n.typ and containsForwardTypeAux(n.sym.typ, seen)):
return true
for i in 0 ..< n.safeLen:
if containsForwardTypeAux(n[i], seen):
return true
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool =
result = false
if t.isNil:
return
if t.kind == tyForward:
return true
if not containsOrIncl(seen, t.id):
if containsForwardTypeAux(t.n, seen):
return true
for i in 0 ..< t.len:
if containsForwardTypeAux(t[i], seen):
return true
proc containsForwardType(arg: PNode): bool =
var seen = initIntSet()
containsForwardTypeAux(arg, seen)
proc containsForwardType(t: PType): bool =
var seen = initIntSet()
containsForwardTypeAux(t, seen)
proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PType =
result = expectedType
field[^1] = semExprWithType(c, field[^1], {efDetermineType, efAllowSymChoice}, result)
if result == nil:
result = field[^1].typ
if c.inGenericContext == 0:
if containsForwardType(field[^1]):
c.forwardFieldUpdates.add (owner, field, result)
else:
fitDefaultNode(c, field[^1], result)
result = field[^1].typ.skipIntLit(c.idgen)
propagateToOwner(owner, result)
proc semDelayedFieldDefault(c: PContext; owner, expectedType: PType; field: PNode) =
resetSemFlag(field[^1])
fitDefaultNode(c, field[^1], expectedType)
propagateToOwner(owner, field[^1].typ.skipIntLit(c.idgen))
proc isRecursiveType*(t: PType): bool =
# handle simple recusive types before typeFinalPass
var cycleDetector = initIntSet()
@@ -550,13 +606,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
var hasDefaultField = a[^1].kind != nkEmpty
if hasDefaultField:
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
if c.inGenericContext > 0:
a[^1] = semExprWithType(c, a[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = a[^1].typ
else:
fitDefaultNode(c, a[^1], typ)
typ = a[^1].typ.skipIntLit(c.idgen)
typ = semFieldDefault(c, result, typ, a)
elif a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
@@ -922,14 +972,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
var hasDefaultField = n[^1].kind != nkEmpty
if hasDefaultField:
typ = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
if c.inGenericContext > 0:
n[^1] = semExprWithType(c, n[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = n[^1].typ
else:
fitDefaultNode(c, n[^1], typ)
typ = n[^1].typ.skipIntLit(c.idgen)
propagateToOwner(rectype, typ)
typ = semFieldDefault(c, rectype, typ, n)
elif n[^2].kind == nkEmpty:
localError(c.config, n.info, errTypeExpected)
typ = errorType(c)
@@ -1072,7 +1115,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
if needsForwardUpdate:
# if the inherited object is a forward type,
# the entire object needs to be checked again
c.forwardTypeUpdates.add (result, n) # we retry in the final pass
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
rawAddSon(result, realBase)
if realBase == nil and tfInheritable in flags:
result.incl tfInheritable
@@ -1720,7 +1763,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
for i in 1..<n.len:
var elem = semGenericParamInInvocation(c, n[i])
addToResult(elem, true)
c.forwardTypeUpdates.add (result, n)
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
return
elif t.kind != tyGenericBody:
# we likely got code of the form TypeA[TypeB] where TypeA is
@@ -1773,10 +1816,14 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
result = newOrPrevType(tyError, prev, c)
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
# isConcrete == false means this generic type is not instanciated here because
# it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are
# unresolved `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can
# instanciate this next time.
# Some generic types like std/options.Option[T] need the kind of the
# given type argument before their fields can be resolved.
# return `tyForward` instead of `tyGenericInvocation` because:
# ```nim
@@ -1792,7 +1839,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
else:
assignType(result, newTypeS(tyForward, c))
result.sym = s
c.forwardTypeUpdates.add (result, n) #fixes 1500
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) #fixes 1500
return
else:
result = instGenericContainer(c, n.info, result,
@@ -2334,7 +2381,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
else:
result = typeExpr.typ.base
if result.isMetaType and
result.kind != tyUserTypeClass:
result.kind notin tyTypeClasses:
# the dot expression may refer to a concept type in
# a different module. allow a normal alias then.
let preprocessed = semGenericStmt(c, n)

View File

@@ -52,7 +52,8 @@ proc searchInstTypes*(g: ModuleGraph; key: PType): PType =
for j in FirstGenericParamAt..<key.kidsLen:
# XXX sameType is not really correct for nested generics?
if not compareTypes(inst[j], key[j],
flags = {ExactGenericParams, PickyCAliases}):
flags = {ExactGenericParams, PickyCAliases,
PickyBackendAliases}):
break matchType
return inst

View File

@@ -784,6 +784,17 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
# if f is metatype.
result = typeRel(c, f, a)
if result == isEqual and
procParamTypeBackendAliases notin c.c.config.legacyFeatures:
# Ensure types that are semantically equal also match at the backend level.
# E.g. reject assigning proc(csize_t) to proc(uint) since these map to
# different C types (size_t vs unsigned long long).
let fCheck = concreteType(c, f)
let aCheck = concreteType(c, a)
if fCheck != nil and aCheck != nil and
not sameBackendTypePickyAliases(fCheck, aCheck):
result = isNone
if result <= isSubrange or inconsistentVarTypes(f, a):
result = isNone
@@ -2834,9 +2845,11 @@ proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
else: break
proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =
template noMatch() =
c.mergeShadowScope #merge so that we don't have to resem for later overloads
if m.calleeSym != nil and m.calleeSym.kind notin {skTemplate, skMacro}:
c.mergeShadowScope
else:
c.closeShadowScope
m.state = csNoMatch
m.firstMismatch.arg = a
m.firstMismatch.formal = formal

View File

@@ -10,7 +10,7 @@
## This module implements threadpool's ``spawn``.
import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings, liftdestructors, renderer
lowerings, liftdestructors, renderer, trees
from trees import getMagic, getRoot
proc callProc(a: PNode): PNode =
@@ -53,6 +53,24 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
if t.kind in {tyVar, tyLent, tySequence}: t = t.elementType
result = not containsGarbageCollectedRef(t)
proc newSpawnMoveStmt(g: ModuleGraph; idgen: IdGenerator; le, ri: PNode): PNode =
let op = getAttachedOp(g, ri.typ.skipTypes({tyGenericInst, tyAlias, tyVar, tySink}), attachedWasMoved)
if op != nil and sfOverridden in op.flags:
result = newNodeI(nkStmtList, le.info)
result.add newFastAsgnStmt(le, ri)
let wasMovedCall = newNodeI(nkCall, ri.info)
wasMovedCall.add newSymNode(op)
if op.typ != nil and op.typ.signatureLen > 1 and op.typ.firstParamType.kind != tyVar:
wasMovedCall.add ri.skipAddr
else:
wasMovedCall.add makeAddr(ri.skipAddr, idgen)
result.add wasMovedCall
else:
result = newFastMoveStmt(g, le, ri)
proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator; owner: PSym; typ: PType;
v: PNode; useShallowCopy=false): PSym =
result = newSym(skTemp, getIdent(g.cache, genPrefix), idgen, owner, varSection.info,
@@ -68,10 +86,10 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
if varInit != nil:
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
# inject destructors pass will do its own analysis
varInit.add newFastMoveStmt(g, newSymNode(result), v)
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
else:
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
varInit.add newFastMoveStmt(g, newSymNode(result), v)
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
else:
let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
deepCopyCall[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))

View File

@@ -118,6 +118,24 @@ proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PNode; isFirstWrite
le.flags.incl nfFirstWrite
result[1] = ri
proc resolveBorrowedRoutineSym(c: PTransf; s: PSym; info: TLineInfo): PSym =
# Follow borrow aliases to the underlying implementation symbol.
var s = s
while true:
# Skips over all borrowed procs getting the last proc symbol without an implementation
let body = getBody(c.graph, s)
if body.kind == nkSym and sfBorrow in body.sym.flags and getBody(c.graph, body.sym).kind == nkSym:
s = body.sym
else:
break
let body = getBody(c.graph, s)
if body.kind == nkSym:
result = body.sym
else:
result = nil
internalError(c.graph.config, info, "wrong AST for borrowed symbol")
proc transformSymAux(c: PTransf, n: PNode): PNode =
let s = n.sym
if s.typ != nil and s.typ.callConv == ccClosure:
@@ -136,17 +154,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
var tc = c.transCon
if sfBorrow in s.flags and s.kind in routineKinds:
# simply exchange the symbol:
var s = s
while true:
# Skips over all borrowed procs getting the last proc symbol without an implementation
let body = getBody(c.graph, s)
if body.kind == nkSym and sfBorrow in body.sym.flags and getBody(c.graph, body.sym).kind == nkSym:
s = body.sym
else:
break
b = getBody(c.graph, s)
if b.kind != nkSym: internalError(c.graph.config, n.info, "wrong AST for borrowed symbol")
b = newSymNode(b.sym, n.info)
b = newSymNode(resolveBorrowedRoutineSym(c, s, n.info), n.info)
elif c.inlining > 0:
# see bug #13596: we use ref-based equality in the DFA for destruction
# injections so we need to ensure unique nodes after iterator inlining
@@ -328,7 +336,7 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
if a.kind == nkSym:
n[1] = transformSymAux(c, a)
return n
of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
of nkLambdaKinds, nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
result = newTransNode(n)
let x = newSymNode(copySym(n[namePos].sym, c.idgen))
c.transCon.mapping[n[namePos].sym.itemId] = x
@@ -694,6 +702,11 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
of nkAddr, nkHiddenAddr:
result = putArgInto(arg[0], formal)
if result == paViaIndirection: result = paFastAsgn
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
if compareTypes(arg.typ, arg[1].typ, dcEqIgnoreDistinct, {IgnoreRangeShallow}):
result = putArgInto(arg[1], formal)
else:
result = paFastAsgn
of nkCurly, nkBracket:
for i in 0..<arg.len:
if putArgInto(arg[i], formal) != paDirectMapping:
@@ -785,7 +798,9 @@ proc transformFor(c: PTransf, n: PNode): PNode =
discard c.breakSyms.pop
let iter = call[0].sym
var iter = call[0].sym
if sfBorrow in iter.flags and iter.kind in routineKinds:
iter = resolveBorrowedRoutineSym(c, iter, n.info)
var v = newNodeI(nkVarSection, n.info)
for i in 0..<n.len - 2:

View File

@@ -897,7 +897,11 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
c.flags = oldFlags
if x == y: return true
let aliasSkipSet = maybeSkipRange({tyAlias})
let aliasSkipSet = maybeSkipRange(
if PickyBackendAliases in c.flags:
{tyInferred}
else:
{tyAlias, tyInferred})
var a = skipTypes(x, aliasSkipSet)
while a.kind == tyUserTypeClass and tfResolved in a.flags:
a = skipTypes(a.last, aliasSkipSet)
@@ -1070,6 +1074,8 @@ proc sameBackendTypeIgnoreRange*(x, y: PType): bool =
result = sameTypeAux(x, y, c)
proc sameBackendTypePickyAliases*(x, y: PType): bool =
let x = x.skipTypes({tyVar, tyLent, tySink, tyOwned})
let y = y.skipTypes({tyVar, tyLent, tySink, tyOwned})
var c = initSameTypeClosure()
c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases}
c.cmp = dcEqIgnoreDistinct

View File

@@ -33,7 +33,7 @@ The text representation is particularly valuable for debugging and introspection
Each ``.nim`` module produces its own ``.nif`` file during compilation.
The NIF format contains:
- **Header** - Version information (e.g., `(.nif26)`)
- **Header** - Version information (e.g., `(.nif27)`)
- **Dependencies** - List of source files and dependencies
- **Interface** - Exported symbols and their indices
- **Body** - The intermediate representation of the module's code in Lisp-like syntax

View File

@@ -34,10 +34,10 @@ To learn how to compile Nim programs and generate documentation see
the [Compiler User Guide](nimc.html) and the [DocGen Tools Guide](docgen.html).
The language constructs are explained using an extended BNF, in which `(a)*`
means 0 or more `a`'s, `a+` means 1 or more `a`'s, and `(a)?` means an
means 0 or more *a*'s, `a+` means 1 or more *a*'s, and `(a)?` means an
optional *a*. Parentheses may be used to group elements.
`&` is the lookahead operator; `&a` means that an `a` is expected but
`&` is the lookahead operator; `&a` means that an *a* is expected but
not consumed. It will be consumed in the following rule.
The `|`, `/` symbols are used to mark alternatives and have the lowest
@@ -1024,6 +1024,9 @@ These are the major type classes:
* procedural type
* generic type
The compiler's internal type zoo is richer than this summary suggests:
some types that are structurally equal still differ in backend representation.
Ordinal types
-------------
@@ -2174,6 +2177,10 @@ Procedural type
A procedural type is internally a pointer to a procedure. `nil` is
an allowed value for a variable of a procedural type.
Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
Examples:
```nim
@@ -8867,7 +8874,7 @@ Byref pragma
The `byref` pragma can be applied to an object or tuple type or a proc param.
When applied to a type it instructs the compiler to pass the type by reference
(hidden pointer) to procs. When applied to a param it will take precedence, even
if the the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
if the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
parameters are marked as `byref` in an `importc` proc, these params translate to pointers.
When an `importcpp` type has a `byref` pragma, these params translate to
C++ references `&`.

View File

@@ -1144,7 +1144,7 @@ there is a difference between the `$` and `repr` outputs:
echo myCharacter, ":", repr(myCharacter)
# --> n:'n'
echo myString, ":", repr(myString)
# --> nim:0x10fa8c050"nim"
# --> nim:"nim"
echo myInteger, ":", repr(myInteger)
# --> 42:42
echo myFloat, ":", repr(myFloat)

View File

@@ -16,10 +16,11 @@ const
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
NimonyStableCommit = "bbfb21529845567c55b67d176354daef0e7d6c29" # unversioned \
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
# Note that Nimony uses Nim as a git submodule but we don't want to install
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
# is **required** here.
# Commit from 2026-05-05
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"

View File

@@ -1559,6 +1559,8 @@ macro expandMacros*(body: typed): untyped =
echo body.toStrLit
result = body
proc getTypeInstSkipAlias(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
proc extractTypeImpl(n: NimNode): NimNode =
## attempts to extract the type definition of the given symbol
case n.kind
@@ -1573,11 +1575,17 @@ proc extractTypeImpl(n: NimNode): NimNode =
result = n[0].getImpl()
of nnkTypeDef:
result = n[2]
if result.kind notin {nnkSym, nnkObjectTy, nnkRefTy, nnkPtrTy, nnkBracketExpr}:
# Handle typeof() and similar unresolvable type expressions
let typSym = if n[0].kind == nnkPragmaExpr: n[0][0] else: n[0]
if typSym.kind == nnkSym:
let resolved = typSym.getTypeInstSkipAlias()
if resolved.kind == nnkSym:
return resolved.getImpl.extractTypeImpl()
error("Invalid node to retrieve type implementation of: " & $result.kind)
else: error("Invalid node to retrieve type implementation of: " & $n.kind)
proc getTypeInstSkipAlias(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
proc customPragmaNode(n: NimNode): NimNode =
result = nil
expectKind(n, {nnkSym, nnkDotExpr, nnkBracketExpr, nnkTypeOfExpr, nnkType, nnkCheckedFieldExpr})
@@ -1618,6 +1626,15 @@ proc customPragmaNode(n: NimNode): NimNode =
var typDef = getImpl(typInst)
while typDef != nil:
typDef.expectKind(nnkTypeDef)
# Resolve typeof() and similar unresolvable type expressions
if typDef[2].kind notin {nnkSym, nnkObjectTy, nnkRefTy, nnkPtrTy, nnkBracketExpr}:
let typSym = if typDef[0].kind == nnkPragmaExpr: typDef[0][0] else: typDef[0]
if typSym.kind == nnkSym:
let resolved = typSym.getTypeInstSkipAlias()
if resolved.kind == nnkSym:
typDef = getImpl(resolved)
continue
break
let typ = typDef[2].extractTypeImpl()
if typ.kind notin {nnkRefTy, nnkPtrTy, nnkObjectTy}: break
let isRef = typ.kind in {nnkRefTy, nnkPtrTy}

View File

@@ -9,6 +9,11 @@
when defined(js):
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
## .. warning:: NRE is deprecated.
## Use [Regex](https://github.com/nitely/nim-regex) or
## `NRE2 <nre2.html>`_ that wraps Regex so that you can easily replace NRE.
## PCRE library is now at end of life.
##
## What is NRE?
## ============
##
@@ -84,7 +89,7 @@ type
Regex* = ref RegexDesc
## Represents the pattern that things are matched against, constructed with
## `re(string)`. Examples: `re"foo"`, `re(r"(*ANYCRLF)(?x)foo #
## comment".`
## comment")`
##
## `pattern: string`
## : the string that was used to create the pattern. For details on how
@@ -154,7 +159,7 @@ type
## will need to pass these as separate flags to PCRE.
RegexMatch* = object
## Usually seen as Option[RegexMatch], it represents the result of an
## Usually seen as `Option[RegexMatch]`, it represents the result of an
## execution. On failure, it is none, on success, it is some.
##
## `pattern: Regex`

View File

@@ -10,6 +10,10 @@
when defined(js):
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
## .. warning:: This module is deprecated.
## Use [Regex](https://github.com/nitely/nim-regex).
## PCRE library is now at end of life.
##
## Regular expression support for Nim.
##
## This module is implemented by providing a wrapper around the

View File

@@ -153,7 +153,7 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
protocol)
result.orig = protocol
i.inc protocol.parseSaturatedNatural(result.major, i)
i.inc # Skip .
if i < protocol.len: inc i # Skip .
i.inc protocol.parseSaturatedNatural(result.minor, i)
proc sendStatus(client: AsyncSocket, status: string): Future[void] =

View File

@@ -128,7 +128,7 @@ proc getContentLength*(): string =
proc getContentType*(): string =
## Returns contents of the `CONTENT_TYPE` environment variable.
return getEnv("CONTENT_Type")
return getEnv("CONTENT_TYPE")
proc getDocumentRoot*(): string =
## Returns contents of the `DOCUMENT_ROOT` environment variable.

View File

@@ -495,13 +495,16 @@ func `$`*[T](c: CritBitTree[T]): string =
const avgItemLen = 16
result = newStringOfCap(c.count * avgItemLen)
result.add("{")
var first = true
when T is void:
for key in keys(c):
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.addQuoted(key)
else:
for key, val in pairs(c):
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.addQuoted(key)
result.add(": ")
result.addQuoted(val)

View File

@@ -454,8 +454,10 @@ proc `$`*[T](deq: Deque[T]): string =
assert $a == "[10, 20, 30]"
result = "["
var first = true
for x in deq:
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -260,7 +260,9 @@ proc `$`*[T](heap: HeapQueue[T]): string =
assert $heap == "[1, 2]"
result = "["
var first = true
for x in heap.data:
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -304,8 +304,10 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string =
assert $a == "[1, 2, 3, 4]"
result = "["
var first = true
for x in nodes(L):
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.addQuoted(x.value)
result.add("]")

View File

@@ -588,7 +588,9 @@ proc handleShortOption(p: var OptParser; cmd: string) =
template next(): untyped = p.cmds[p.idx + 1]
let canTakeVal = card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal
let canTakeVal = card(p.shortNoVal) > 0 and
p.key.len > 0 and p.key[0] notin p.shortNoVal
if i < cmd.len and cmd[i] in p.separators:
# separator case
if prShortAllowSep in p.rules:

View File

@@ -1668,7 +1668,10 @@ func getSymbol(c: var PegLexer, tok: var Token) =
while pos < c.buf.len:
add(tok.literal, c.buf[pos])
inc(pos)
if pos < c.buf.len and c.buf[pos] notin strutils.IdentChars: break
if pos < c.buf.len:
let ch = c.buf[pos]
# Keep non-ASCII bytes so UTF-8 terminals reach the rune-aware matchers.
if ch notin strutils.IdentChars and ord(ch) < 0x80: break
c.bufpos = pos
tok.kind = tkIdentifier

View File

@@ -36,6 +36,8 @@ elif defined(linux):
# Android
"/data/data/com.termux/files/usr/etc/tls/cert.pem",
"/system/etc/security/cacerts",
# Nix
"/etc/ssl/certs/ca-bundle.crt"
]
elif defined(bsd):
const certificatePaths = [

View File

@@ -259,7 +259,7 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
result = s.readDataStrImpl(s, buffer, slice)
else:
# fallback
result = s.readData(beginStore(buffer, slice.b + 1 - slice.a, slice.a), slice.b + 1 - slice.a)
result = s.readData(beginStore(buffer, buffer.len, slice.a), slice.b + 1 - slice.a)
endStore(buffer)
template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped =
@@ -1226,7 +1226,7 @@ else: # after 1.3 or JS not defined
jsOrVmBlock:
buffer[slice.a..<slice.a+result] = s.data[s.pos..<s.pos+result]
do:
copyMem(beginStore(buffer, result, slice.a), readRawData(s.data, s.pos), result)
copyMem(beginStore(buffer, buffer.len, slice.a), readRawData(s.data, s.pos), result)
endStore(buffer)
inc(s.pos, result)
else:
@@ -1267,16 +1267,16 @@ else: # after 1.3 or JS not defined
var s = StringStream(s)
if bufLen <= 0:
return
if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen)
when defined(js):
if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen)
try:
s.data[s.pos..<s.pos+bufLen] = cast[ptr string](buffer)[][0..<bufLen]
except:
raise newException(Defect, "could not write to string stream, " &
"did you use a non-string buffer pointer?", getCurrentException())
elif not defined(nimscript):
copyMem(beginStore(s.data, bufLen, s.pos), buffer, bufLen)
copyMem(beginStore(s.data, s.pos + bufLen, s.pos), buffer, bufLen)
endStore(s.data)
inc(s.pos, bufLen)
@@ -1346,7 +1346,7 @@ proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
let len = slice.b + 1 - slice.a
result = readBuffer(FileStream(s).f, beginStore(buffer, len, slice.a), len)
result = readBuffer(FileStream(s).f, beginStore(buffer, buffer.len, slice.a), len)
endStore(buffer)
proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =

View File

@@ -380,8 +380,10 @@ proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
result = "{:}"
else:
result = "{"
var first = true
for key, val in pairs(t):
if result.len > 1: result.add(", ")
if first: first = false
else: result.add(", ")
result.add(key)
result.add(": ")
result.add(val)

View File

@@ -18,12 +18,12 @@ proc addCstringN(result: var string, buf: cstring; buflen: int) =
# no nimvm support needed, so it doesn't need to be fast here either
let oldLen = result.len
let newLen = oldLen + buflen
result.setLen newLen
{.cast(noSideEffect).}:
when declared(completeStore):
c_memcpy(beginStore(result, buflen, oldLen), buf, buflen.csize_t)
when declared(beginStore):
c_memcpy(beginStore(result, newLen, oldLen), buf, buflen.csize_t)
endStore(result)
else:
result.setLen newLen
discard c_memcpy(result[oldLen].addr, buf, buflen.csize_t)
import std/private/[dragonbox, schubfach]

344
lib/std/nre2.nim Normal file
View File

@@ -0,0 +1,344 @@
#
# Nim's Runtime Library
# (c) Copyright 2026 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## What is NRE2?
## =============
##
## A regular expression library for Nim to replace deprecated NRE.
## It is implemented with `Regex<https://github.com/nitely/nim-regex>`_ ,
## that is pure Nim regex engine and guarantees linear time matching.
## It supports compiling regex and matching at compile-time and
## works with JS backend.
##
## NRE2 is mostly compatible with NRE and the syntax of regular expression is similar to PCRE.
## But it lacks a few features and how to set options in a pattern is different.
##
## The syntax of regular expression is explained in https://nitely.github.io/nim-regex/regex.html
runnableExamples:
import std/sugar
let vowels = re"[aeoui]"
let bounds = collect:
for match in "moiga".findIter(vowels): match.matchBounds
assert bounds == @[1 .. 1, 2 .. 2, 4 .. 4]
from std/sequtils import toSeq
let s = sequtils.toSeq("moiga".findIter(vowels))
# fully qualified to avoid confusion with nre.toSeq
assert s.len == 3
let firstVowel = "foo".find(vowels)
let hasVowel = firstVowel.isSome()
assert hasVowel
let matchBounds = firstVowel.get().captureBounds[-1]
assert matchBounds.a == 1
# as with module `re`, unless specified otherwise, `start` parameter in each
# proc indicates where the scan starts, but outputs are relative to the start
# of the input string, not to `start`:
assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab"
assert find("uxabc", re"ab", start = 3).isNone
import std/[options, tables]
import regex, regex/nfatype
export options
export regex.RegexFlags, regex.RegexError
type
Regex* = regex.Regex2
## Represents the pattern that things are matched against, constructed with
## `re(string)`. Examples: `re"foo"`, `re(r"(?x)foo #comment")`
##
## `captureCount: int`
## : the number of captures that the pattern has.
##
## `captureNameId: Table[string, int]`
## : a table from the capture names to their numeric id.
##
## The syntax of regular expression of Regex is explained in https://nitely.github.io/nim-regex/regex.html
RegexMatch* = object
## Usually seen as `Option[RegexMatch]`, it represents the result of an
## execution. On failure, it is none, on success, it is some.
##
## `str: string`
## : the string that was matched against
##
## `captures[]: string`
## : the string value of whatever was captured at that id. If the value
## is invalid, then behavior is undefined. If the id is `-1`, then
## the whole match is returned. If the given capture was not matched,
## `nil` is returned. See examples for `match`.
##
## `captureBounds[]: HSlice[int, int]`
## : gets the bounds of the given capture according to the same rules as
## the above. If the capture is not filled, then `None` is returned.
## The bounds are both inclusive. See examples for `match`.
##
## `match: string`
## : the full text of the match.
##
## `matchBounds: HSlice[int, int]`
## : the bounds of the match, as in `captureBounds[]`
##
## `(captureBounds|captures).toTable`
## : returns a table with each named capture as a key.
##
## `(captureBounds|captures).toSeq`
## : returns all the captures by their number.
##
## `$: string`
## : same as `match`
str*: string ## The string that was matched against.
matchImpl: regex.RegexMatch2
Captures* {.borrow: `.`.} = distinct RegexMatch
CaptureBounds* {.borrow: `.`.} = distinct RegexMatch
func captureCount*(pattern: Regex): int {.inline.} =
pattern.toRegex().groupsCount
func captureNameId*(pattern: Regex): Table[string, int] =
result = initTable[string, int](pattern.toRegex().namedGroups.len)
for k, v in pattern.toRegex().namedGroups:
result[k] = v
func captureBounds*(match: RegexMatch): CaptureBounds {.inline.} =
CaptureBounds(match)
func captures*(match: RegexMatch): Captures {.inline.} =
Captures(match)
func contains*(match: Captures or CaptureBounds, i: int): bool {.inline.} =
i >= -1 and i < match.matchImpl.groupsCount and match.matchImpl.group(i) != reNonCapture
func len*(match: Captures or CaptureBounds): int {.inline.} =
## Return the number of capturing groups
match.matchImpl.groupsCount
func `[]`*(match: CaptureBounds; i: int): HSlice[int, int] {.inline.} =
if i == -1: match.matchImpl.boundaries else: match.matchImpl.group(i)
func `[]`*(match: CaptureBounds; name: string): HSlice[int, int] {.inline.} =
result = match.matchImpl.group(name)
if result == reNonCapture:
raise newException(KeyError, "Group '" & name & "' was not captured")
func `[]`*(match: Captures; i: int): string {.inline.} =
match.str[CaptureBounds(match)[i]]
func `[]`*(match: Captures, name: string): string {.inline.} =
match.str[CaptureBounds(match)[name]]
func match*(match: RegexMatch): string {.inline.} =
match.str[match.matchImpl.boundaries]
func matchBounds*(match: RegexMatch): HSlice[int, int] {.inline.} =
match.matchImpl.boundaries
func contains*(match: CaptureBounds or Captures, name: string): bool {.inline.} =
name in match.matchImpl.namedGroups and
match.matchImpl.group(name) != reNonCapture
func toTable*(match: Captures): Table[string, string] =
result = initTable[string, string]()
for k, i in match.matchImpl.namedGroups:
let r = match.matchImpl.group(i)
if r != reNonCapture:
result[k] = match.str[r]
func toTable*(match: CaptureBounds): Table[string, HSlice[int, int]] =
result = initTable[string, HSlice[int, int]]()
for k, i in match.matchImpl.namedGroups:
let r = match.matchImpl.group(i)
if r != reNonCapture:
result[k] = match.matchImpl.group(i)
iterator items*(match: CaptureBounds; default = none(HSlice[int, int])): Option[HSlice[int, int]] =
for i in 0 ..< match.len:
yield if i in match: some(match[i]) else: default
iterator items*(match: Captures; default = none(string)): Option[string] =
for i in 0 ..< match.len:
yield if i in match: some(match[i]) else: default
func toSeq*(match: CaptureBounds;
default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] =
result = @[]
for it in match.items(default): result.add it
func toSeq*(match: Captures;
default: Option[string] = none(string)): seq[Option[string]] =
result = @[]
for it in match.items(default): result.add it
func `$`*(match: RegexMatch): string =
match.match
func re*(pattern: static string; flags: static RegexFlags = {}): static[Regex2] =
## Parse and compile a regular expression at compile-time
result = regex.re2(pattern, flags)
func re*(pattern: string; flags: RegexFlags = {}): Regex =
## Parse and compile a regular expression at run-time
result = regex.re2(pattern, flags)
func match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the
## string.
runnableExamples:
assert "foo".match(re"f").isSome
assert "foo".match(re"o").isNone
assert "abc".match(re"(\w)").get.captures[0] == "a"
assert "abc".match(re"(?P<letter>\w)").get.captures["letter"] == "a"
assert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
assert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
assert 0 in "abc".match(re"(\w)").get.captureBounds
assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
var mat = default(RegexMatch)
let r = regex.startsWith(str.toOpenArray(0, min(str.high, endpos)), pattern, mat.matchImpl, start)
if r:
mat.str = str
some(mat)
else:
none(RegexMatch)
iterator findIter*(str: string; pattern: Regex; start = 0, endpos = int.high): RegexMatch =
## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every
## non-overlapping match:
runnableExamples:
import std/sugar
assert collect(for a in "2222".findIter(re"22"): a.match) == @["22", "22"]
# not @["22", "22", "22"]
## Arguments are the same as `find(...)<#find,string,Regex,int>`_
##
## Variants:
##
## - `proc findAll(...)` returns a `seq[string]`
var mat = RegexMatch(str: str)
# TODO:
# needs following PR to remove `substr` call.
# https://github.com/nitely/nim-regex/pull/162
for m in regex.findAll(str.substr(start, endpos), pattern):
mat.matchImpl = m
yield mat
proc find*(str: string; pattern: Regex; start = 0; endpos = int.high): Option[RegexMatch] =
## Finds the given pattern in the string between the end and start
## positions.
##
## `start`
## : The start point at which to start matching. `|abc` is `0`;
## `a|bc` is `1`
##
## `endpos`
## : The maximum index for a match; `int.high` means the end of the
## string, otherwise its an inclusive upper bound.
var mat = default(RegexMatch)
let r = regex.find(str.substr(start, endpos), pattern, mat.matchImpl)
# remove following code after regex.find get `start`/`last` parameter
for v in mat.matchImpl.captures.mitems:
v.a += start
v.b += start
mat.matchImpl.boundaries.a += start
mat.matchImpl.boundaries.b += start
if r:
mat.str = str
some(mat)
else:
none(RegexMatch)
proc findAll*(str: string; pattern: Regex; start = 0; endpos = int.high): seq[string] =
result = @[]
for match in str.findIter(pattern, start, endpos):
result.add(match.match)
proc contains*(str: string; pattern: Regex; start = 0; endpos = int.high): bool =
## Determine if the string contains the given pattern between the end and
## start positions:
## This function is equivalent to `isSome(str.find(pattern, start, endpos))`.
runnableExamples:
assert "abc".contains(re"bc")
assert not "abc".contains(re"cd")
assert not "abc".contains(re"a", start = 1)
isSome(str.find(pattern, start, endpos))
proc split*(str: string; pattern: Regex; maxSplit = -1; start = 0): seq[string] =
## Splits the string with the given regex. This works according to the
## rules that Perl and Javascript use.
##
## `start` behaves the same as in `find(...)<#find,string,Regex,int>`_.
##
runnableExamples:
# - If the match is zero-width, then the string is still split:
assert "123".split(re"") == @["1", "2", "3"]
# - If the pattern has a capture in it, it is added after the string
# split:
assert "12".split(re"(\d)") == @["", "1", "", "2", ""]
# - If `maxsplit != -1`, then the string will only be split
# `maxsplit - 1` times. This means that there will be `maxsplit`
# strings in the output seq.
assert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]
result = splitIncl(str, pattern, maxSplit, start)
proc replace*(str: string; pattern: Regex;
subproc: proc (match: RegexMatch): string): string =
## Replaces each match of Regex in the string with `subproc`, which should
## never be or return `nil`.
##
## If `subproc` is a `proc (RegexMatch): string`, then it is executed with
## each match and the return value is the replacement value.
##
## If `subproc` is a `proc (string): string`, then it is executed with the
## full text of the match and the return value is the replacement value.
##
## If `subproc` is a string, the syntax is as follows:
##
## - `$$` - literal `$`
## - `$123` - capture number `123`
## - `$1$#` - first and second captures
## - `$#` - first capture
##
## Following syntax is not supported in NRE2
##
## - `$foo` - named capture `foo`
## - `${foo}` - same as above
## - `$0` - full match
##
## If a given capture is missing, `ValueError` is thrown.
proc by(m: RegexMatch2, s: string): string =
let mat = RegexMatch(str: s, matchImpl: m)
result = subproc(mat)
result = regex.replace(str, pattern, by)
proc replace*(str: string; pattern: Regex;
subproc: proc (match: string): string): string =
proc by(m: RegexMatch2; s: string): string =
result = subproc(s)
result = regex.replace(str, pattern, by)
proc replace*(str: string; pattern: Regex; sub: string): string =
result = regex.replace(str, pattern, sub)
func escapeRe*(str: string): string =
## Escapes the string so it doesn't match any special characters.
runnableExamples:
assert escapeRe("fly+wind") == "fly\\+wind"
assert escapeRe("nim*") == "nim\\*"
result = regex.escapeRe(str)

14
lib/std/nre2.nims Normal file
View File

@@ -0,0 +1,14 @@
import std/os
if getCommand() == "doc":
# std/nre2 requires nim-regex and it requires nim-unicodedb.
# when build documentation on CI, git clone them as nimble is not available
const PkgDir = "build/deps"
const Pkgs = ["nim-regex", "nim-unicodedb"]
for n in Pkgs:
if not dirExists(PkgDir / n):
exec("git clone -q https://github.com/nitely/" & n & " " & (PkgDir / n))
switch("path", "$nim" / PkgDir / n / "src")

View File

@@ -294,7 +294,11 @@ proc containsOrIncl*[A](s: var PackedSet[A], key: A): bool =
for i in 0..<s.elems:
if s.a[i] == ord(key):
return true
incl(s, key)
if s.elems < s.a.len:
s.a[s.elems] = ord(key)
inc(s.elems)
else:
incl(s, key)
result = false
else:
var t = packedSetGet(s, ord(key) shr TrunkShift)

View File

@@ -84,7 +84,7 @@ func setSlice*(s: var string, slice: Slice[int]) =
when not declared(moveMem):
impl()
else:
let p = beginStore(s, last - first + 1)
let p = beginStore(s, s.len)
moveMem(p, addr p[first], last - first + 1)
endStore(s)
s.setLen(last - first + 1)

View File

@@ -485,7 +485,7 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
while true:
# fixes #9634; this pattern may need to be abstracted as a template if reused;
# likely other io procs need this for correctness.
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, sp, pos)), sp.cint, f) != nil
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, pos + sp, pos)), sp.cint, f) != nil
endStore(line)
if fgetsSuccess: break
when not defined(nimscript):

View File

@@ -166,7 +166,7 @@ proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.}
## it was "moved" and to signify its destructor should do nothing and
## ideally be optimized away.
proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} =
proc move*[T](x: var T): T {.magic: "Move", noSideEffect, nodestroy.} =
result = x
{.cast(raises: []), cast(tags: []).}:
`=wasMoved`(x)
@@ -1703,7 +1703,8 @@ when not (notJSnotNims and defined(nimSeqsV2)):
# Needed so modules imported by system (e.g. syncio) can reference these without guards.
when notJSnotNims:
# mm:refc: string = ptr NimStringDesc with data: UncheckedArray[char]
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
{.cast(noSideEffect).}: s.setLen(newLen)
let ns = cast[NimString](s)
if ns == nil: nil
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
@@ -1714,7 +1715,7 @@ when not (notJSnotNims and defined(nimSeqsV2)):
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
else:
# JS/nimscript: callers are guarded by whenNotVmJsNims/when not defined(js)
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} = discard
template readRawData*(s: string; start = 0): ptr UncheckedArray[char] = nil
@@ -2418,6 +2419,33 @@ when notJSnotNims and hasAlloc:
when not defined(nimV2):
include "system/repr"
func setLenUninit*(s: var string, newlen: Natural) {.nodestroy.} =
## Sets the length of string `s` to `newlen`.
## New slots will not be initialized.
##
## If the new length is smaller than the new length,
## `s` will be truncated.
let n = max(newLen, 0)
when nimvm:
s.setLen(n)
else:
when notJSnotNims:
when defined(nimSeqsV2):
{.noSideEffect.}:
let str = unsafeAddr s
when defined(nimsso):
setLengthStrV3Uninit(cast[ptr SmallString](str)[], newlen)
else:
setLengthStrV2Uninit(cast[ptr NimStringV2](str)[], newlen)
else:
{.noSideEffect.}:
when hasAlloc:
setLengthStrUninit(s, newlen)
else:
s.setLen(n)
else: s.setLen(n)
when notJSnotNims and hasThreadSupport and hostOS != "standalone":
when not defined(nimPreviewSlimSystem):
include "system/channels_builtin"
@@ -2666,7 +2694,9 @@ when hasAlloc or defined(nimscript):
setLen(x, xl+item.len)
var j = xl-1
while j >= i:
when defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
when defined(nimsso):
x[j+item.len] = x[j]
elif defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
x[j+item.len] = move x[j]
else:
shallowCopy(x[j+item.len], x[j])

View File

@@ -691,7 +691,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
removeChunkFromMatrix2(a, result, fl, sl)
if result.size >= size + PageSize:
splitChunk(a, result, size)
# set 'used' to to true:
# set 'used' to true:
result.prevSize = 1
track("setUsedToFalse", addr result.size, sizeof(int))
sysAssert result.owner == addr a, "getBigChunk: No owner set!"
@@ -708,7 +708,7 @@ proc getHugeChunk(a: var MemRegion; size: int): PBigChunk =
result.next = nil
result.prev = nil
result.size = size
# set 'used' to to true:
# set 'used' to true:
result.prevSize = 1
result.owner = addr a
incl(a, a.chunkStarts, pageIndex(result))

View File

@@ -143,7 +143,7 @@ when nimCoroutines:
proc find(first: var GcStack, bottom: pointer): ptr GcStack =
## Find stack struct based on bottom pointer. If `bottom` is nil then main
## thread stack is is returned.
## thread stack is returned.
if bottom == nil:
return addr(gch.stack)

View File

@@ -59,16 +59,35 @@ template `[]=`*(s: string; i: int; val: char) = arrPut(s, i, val)
template `^^`(s, i: untyped): untyped =
(when i is BackwardsIndex: s.len - int(i) else: int(i))
template spliceImpl(s, a, L, b: typed): untyped =
template spliceStringImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len): movingCopy(s[i], s[i-shift])
for i in countdown(newLen-1, a+b.len):
s[i] = s[i-shift]
else:
for i in countup(a+b.len, newLen-1): movingCopy(s[i], s[i-shift])
for i in countup(a+b.len, newLen-1):
s[i] = s[i-shift]
# cut down:
setLen(s, newLen)
# fill the hole:
for i in 0 ..< b.len: s[a+i] = b[i]
template spliceSeqImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len):
movingCopy(s[i], s[i-shift])
else:
for i in countup(a+b.len, newLen-1):
movingCopy(s[i], s[i-shift])
# cut down:
setLen(s, newLen)
# fill the hole:
@@ -102,7 +121,7 @@ proc `[]=`*[T, U: Ordinal](s: var string, x: HSlice[T, U], b: string) {.systemRa
if L == b.len:
for i in 0..<L: s[i+a] = b[i]
else:
spliceImpl(s, a, L, b)
spliceStringImpl(s, a, L, b)
proc `[]`*[Idx, T; U, V: Ordinal](a: array[Idx, T], x: HSlice[U, V]): seq[T] {.systemRaisesDefect.} =
## Slice operation for arrays.
@@ -162,4 +181,4 @@ proc `[]=`*[T; U, V: Ordinal](s: var seq[T], x: HSlice[U, V], b: openArray[T]) {
if L == b.len:
for i in 0 ..< L: s[i+a] = b[i]
else:
spliceImpl(s, a, L, b)
spliceSeqImpl(s, a, L, b)

View File

@@ -158,6 +158,26 @@ proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
s.p.data[newLen] = '\0'
s.len = newLen
proc setLengthStrV2Uninit(s: var NimStringV2, newLen: int) =
if newLen == 0:
discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations"
else:
if isLiteral(s):
let oldP = s.p
s.p = allocPayload(newLen)
s.p.cap = newLen
if s.len > 0:
copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
s.p.data[newLen] = '\0'
elif newLen > s.len:
let oldCap = s.p.cap and not strlitFlag
if newLen > oldCap:
let newCap = max(newLen, resize(oldCap))
s.p = reallocPayload0(s.p, oldCap, newCap)
s.p.cap = newCap
s.p.data[newLen] = '\0'
s.len = newLen
proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
if a.p == b.p and a.len == b.len: return
if isLiteral(b):
@@ -216,13 +236,17 @@ func capacity*(self: string): int {.inline.} =
let str = cast[ptr NimStringV2](unsafeAddr self)
result = if str.p != nil: str.p.cap and not strlitFlag else: 0
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
## Returns a writable pointer for bulk write of `ensuredLen` bytes starting at `start`.
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
## ownership, and returns a pointer to s[start] for bulk writing.
## Call `endStore(s)` afterwards for portability.
{.cast(noSideEffect).}: prepareMutation(s)
let str = cast[ptr NimStringV2](unsafeAddr s)
if str.p == nil: nil
else: cast[ptr UncheckedArray[char]](addr str.p.data[start])
## To keep the current length, pass `s.len`.
{.cast(noSideEffect).}:
let p = cast[ptr NimStringV2](addr s)
setLengthStrV2Uninit(p[], newLen)
prepareMutation(s)
if p.p == nil: nil
else: cast[ptr UncheckedArray[char]](addr p.p.data[start])
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
## No-op for non-SSO strings; call after bulk writes via `beginStore`.

View File

@@ -224,13 +224,14 @@ proc cmpStringPtrs(a, b: ptr SmallString): int {.inline.} =
minLen - AlwaysAvail)
if result == 0: result = aslen - bslen
return
# At least one is long. Hot prefix: inlinePtr[0..AlwaysAvail-1] mirrors heap data.
let pfxLen = min(min(aslen, bslen), AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
# At least one is long. Hot prefix mirrors heap data, but only up to fullLen:
# shrinking can leave stale bytes in the inline cache past the logical length.
let la = if aslen > PayloadSize: a.more.fullLen else: aslen
let lb = if bslen > PayloadSize: b.more.fullLen else: bslen
let minLen = min(la, lb)
let pfxLen = min(minLen, AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
if minLen <= AlwaysAvail:
result = la - lb
return
@@ -496,28 +497,75 @@ proc mnewString(len: int): SmallString {.compilerproc.} =
result.more = p
setSSLen(result, HeapSlen)
proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
## Sets the length of s to newLen, zeroing new bytes on growth.
proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
# Shared implementation for setLengthStrV2 (zeroing) and setLengthStrV3Uninit
# Difference between the two modes:
# - inline/medium -> long growth: alloc0 (zeroing) vs alloc (uninit)
# - long -> long growth: zeroMem the new tail (zeroing) or skip it (uninit)
let slen = ssLen(s)
let curLen = if slen > PayloadSize: s.more.fullLen else: slen
if newLen == curLen: return
if newLen <= 0:
if newLen < curLen:
# Shrinking:
if slen > PayloadSize:
if slen == HeapSlen and s.more.rc == 1:
s.more.fullLen = 0
s.more.data[0] = '\0'
# Unique heap block: keep the buffer allocated to avoid alloc/dealloc
# ping-pong when callers shrink then grow (e.g. setLen(0) + add loops).
s.more.fullLen = newLen
s.more.data[newLen] = '\0'
else:
# shared or static block: detach and go back to empty inline
nimDestroyStrV1(s)
s.bytes = 0 # slen=0, all inline chars zeroed
# shared or static block: detach and go back to inline
if newLen <= 0:
nimDestroyStrV1(s)
s.bytes = 0
else:
let old = s.more
let inl = inlinePtr(s)
copyMem(inl, addr old.data[0], newLen)
inl[newLen] = '\0'
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
dealloc(old)
if newLen < AlwaysAvail:
when system.cpuEndian == littleEndian:
let keepBits = (newLen + 1) * 8
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
s.bytes = (s.bytes and charMask) or uint(newLen)
else:
let discardBits = (AlwaysAvail - newLen) * 8
let slenBit = 8 * (sizeof(uint) - 1)
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
else:
setSSLen(s, newLen)
else:
s.bytes = 0 # slen=0, all inline chars zeroed (SWAR safe)
# inline/medium shrink
if newLen <= 0:
s.bytes = 0
else:
let inl = inlinePtr(s)
inl[newLen] = '\0'
if newLen < AlwaysAvail:
when system.cpuEndian == littleEndian:
let keepBits = (newLen + 1) * 8
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
s.bytes = (s.bytes and charMask) or uint(newLen)
else:
let discardBits = (AlwaysAvail - newLen) * 8
let slenBit = 8 * (sizeof(uint) - 1)
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
else:
setSSLen(s, newLen)
return
if slen <= PayloadSize:
if newLen <= PayloadSize:
let inl = inlinePtr(s)
if newLen > curLen:
zeroMem(addr inl[curLen], newLen - curLen)
# Grow within inline/medium
# Bytes above newLen already zero by the SWAR invariant,
# so setSSLen is sufficient.
if zeroing:
zeroMem(addr inl[curLen], newLen - curLen)
inl[newLen] = '\0'
setSSLen(s, newLen)
else:
@@ -542,43 +590,33 @@ proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
else:
# grow into long
let newCap = resize(newLen)
let p = cast[ptr LongString](alloc0(LongStringDataOffset + newCap + 1))
let p = if zeroing:
# bytes [curLen..newLen] and p.data[newLen] zeroed by alloc0
cast[ptr LongString](alloc0(LongStringDataOffset + newCap + 1))
else:
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
p.data[newLen] = '\0'
p
p.rc = 1
p.fullLen = newLen
p.capImpl = newCap
copyMem(addr p.data[0], inlinePtr(s), curLen)
# bytes [curLen..newLen] zeroed by alloc0; p.data[newLen] = '\0' by alloc0
s.more = p
setSSLen(s, HeapSlen)
else:
# currently long
if newLen <= PayloadSize:
# shrink back to inline
let old = s.more
let inl = inlinePtr(s)
copyMem(inl, addr old.data[0], newLen)
inl[newLen] = '\0'
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
dealloc(old)
# Zero padding bytes in `bytes` for SWAR invariant
if newLen < AlwaysAvail:
when system.cpuEndian == littleEndian:
let keepBits = (newLen + 1) * 8
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
s.bytes = (s.bytes and charMask) or uint(newLen)
else:
let discardBits = (AlwaysAvail - newLen) * 8
let slenBit = 8 * (sizeof(uint) - 1)
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
else:
setSSLen(s, newLen)
else:
ensureUniqueLong(s, curLen, newLen)
if newLen > curLen:
zeroMem(addr s.more.data[curLen], newLen - curLen)
s.more.data[newLen] = '\0'
s.more.fullLen = newLen
# currently long: grow within the heap buffer (shrinking already returned above)
ensureUniqueLong(s, curLen, newLen) # sets fullLen = newLen
if zeroing and newLen > curLen:
zeroMem(addr s.more.data[curLen], newLen - curLen)
s.more.data[newLen] = '\0'
proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
## Sets the length of `s` to `newLen`, zeroing new bytes on growth.
setLengthStr(s, newLen, zeroing = true)
proc setLengthStrV3Uninit(s: var SmallString; newLen: int) {.compilerRtl.} =
## Sets the length of `s` to `newLen`, NOT zeroing new bytes on growth.
setLengthStr(s, newLen, zeroing = false)
proc nimAsgnStrV2(a: var SmallString; b: SmallString) {.compilerRtl, inline.} =
if ssLen(b) <= PayloadSize:
@@ -684,18 +722,37 @@ proc completeStore(s: var SmallString) {.compilerproc, inline.} =
proc completeStore*(s: var string) {.inline.} =
completeStore(cast[ptr SmallString](addr s)[])
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
## Prepares `s` for a bulk write of `ensuredLen` bytes starting at `start`.
## The caller must ensure `s.len >= start + ensuredLen` (e.g. via `newString` or `setLen`).
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
## ownership, and returns a pointer to s[start] for bulk writing.
## Call `endStore(s)` afterwards to sync the inline cache.
## To keep the current length, pass `s.len`.
{.cast(noSideEffect).}:
let ss = cast[ptr SmallString](addr s)
let slen = ssLen(ss[])
if slen > PayloadSize:
ensureUniqueLong(ss[], ss[].more.fullLen, ss[].more.fullLen)
let curLen = if slen > PayloadSize: ss[].more.fullLen else: slen
if newLen <= PayloadSize and slen <= PayloadSize:
# Stay inline/medium.
if newLen != curLen:
setSSLen(ss[], newLen)
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
elif slen <= PayloadSize:
# Inline/medium → long.
let newCap = resize(newLen)
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
p.rc = 1
p.fullLen = newLen
p.capImpl = newCap
copyMem(addr p.data[0], inlinePtr(ss[]), curLen)
p.data[newLen] = '\0'
ss[].more = p
setSSLen(ss[], HeapSlen)
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
else:
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
# Already long: resize within heap (no transition back to inline).
ensureUniqueLong(ss[], curLen, newLen)
ss[].more.data[newLen] = '\0'
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
## Syncs the inline cache after bulk writes via `beginStore`. No-op for short/medium strings.

View File

@@ -244,6 +244,31 @@ proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
result.len = n
result.data[n] = '\0'
proc setLengthStrUninit(s: var string, newlen: Natural) {.nodestroy.} =
## Sets the `s` length to `newlen` without zeroing memory on growth.
## Terminating zero for cstring compatibility is set.
var str = cast[NimString](s)
let n = max(newLen, 0)
if str == nil:
if n == 0: return
else:
str = rawNewStringNoInit(n)
str.data[n] = '\0'
str.len = n
s = cast[string](str)
else:
if n > str.space:
let sp = max(resize(str.space), n)
str = rawNewStringNoInit(sp)
copyMem(addr str.data[0], unsafeAddr s[0], s.len)
str.data[n] = '\0'
str.len = n
s = cast[string](str)
elif n != s.len:
str.data[n] = '\0'
str.len = n
else: return
# ----------------- sequences ----------------------------------------------
proc incrSeq(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} =
@@ -274,12 +299,22 @@ proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerproc.} =
# since we steal the content from 's', it's crucial to set s's len to 0.
s.len = 0
proc newSeqUninitRaw(typ: PNimType; len: int): pointer {.inline.} =
## Creates a sequence payload with capacity and length `len` without
## forcing zero-initialization for `ntfNoRefs` element types.
result = nimNewSeqOfCap(typ, len)
cast[PGenericSeq](result).len = len
proc extendCapacityRaw(src: PGenericSeq; typ: PNimType;
elemSize, elemAlign, newLen: int): PGenericSeq {.inline.} =
elemSize, elemAlign, newLen: int;
doInit: static bool): PGenericSeq {.inline.} =
## Reallocs `src` to fit `newLen` elements without any checks.
## Capacity always increases to at least next `resize` step.
let newCap = max(resize(src.space), newLen)
result = cast[PGenericSeq](newSeq(typ, newCap))
when doInit:
result = cast[PGenericSeq](newSeq(typ, newCap))
else:
result = cast[PGenericSeq](newSeqUninitRaw(typ, newCap))
copyMem(dataPointer(result, elemAlign), dataPointer(src, elemAlign), src.len * elemSize)
# since we steal the content from 's', it's crucial to set s's len to 0.
src.len = 0
@@ -310,15 +345,19 @@ proc truncateRaw(src: PGenericSeq; baseFlags: set[TNimTypeFlag]; isTrivial: bool
((result.len-%newLen) *% elemSize))
template setLengthSeqImpl(s: PGenericSeq, typ: PNimType, newLen: int; isTrivial: bool;
doInit: static bool) =
doInit: static bool) =
if s == nil:
if newLen == 0: return s
else: return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
else:
when doInit:
return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
else:
return cast[PGenericSeq](newSeqUninitRaw(typ, newLen))
else:
let elemSize = typ.base.size
let elemAlign = typ.base.align
result = if newLen > s.space:
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen)
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen, doInit)
elif newLen < s.len:
s.truncateRaw(typ.base.flags, isTrivial, elemSize, elemAlign, newLen)
else:

View File

@@ -39,7 +39,7 @@ architecture combinations:
|--------------------------------|----------------------------------------|
| Windows (Windows XP or greater) | x86 and x86_64 |
| Linux (most distributions) | x86, x86_64, ppc64, and armv6l |
| Mac OS X (10.04 or greater) | x86, x86_64, ppc64, and Apple Silicon (ARM64) |
| Mac OS X (10.4 or greater) | x86, x86_64, ppc64, and Apple Silicon (ARM64) |
More platforms are supported, however, they are not tested regularly and they
may not be as stable as the above-listed platforms.

View File

@@ -0,0 +1,33 @@
discard """
cmd: '''nim c --mm:arc --expandArc:foo $file'''
nimout: '''
--expandArc: foo
var broken_cursor
block :tmp:
var i
var i_1 = 0
let L = len(seq[Large](broken_cursor))
block :tmp_1:
while i_1 < L:
i = seq[Large](broken_cursor)[i_1]
discard i
{.push, overflowChecks: false.}
inc(i_1, 1)
{.pop.}
-- end of expandArc ------------------------
'''
"""
type
Large = array[1024, byte]
List = distinct seq[Large]
proc foo =
var
broken: List
for i in seq[Large](broken):
discard i
foo()

View File

@@ -0,0 +1,45 @@
discard """
output: '''
246
246
'''
"""
# issue #25730
type
Inner[T] = object
x: T
Foo[T] = object
inner: Inner[T]
Bar[T] = object
foo: Foo[T]
proc `=sink`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} =
a.x = b.x * 2
proc `=copy`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} =
a.x = b.x * 2
when true:
proc `=sink`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} =
`=sink`(a.foo, b.foo)
proc `=copy`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} =
`=copy`(a.foo, b.foo)
proc useSink() =
let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123)))
var b: Bar[int]
`=sink`(b, a)
echo b.foo.inner.x
useSink()
proc useCopy() =
let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123)))
var b: Bar[int]
`=copy`(b, a)
echo b.foo.inner.x
useCopy()

7
tests/ccgbugs2/m25800.h Normal file
View File

@@ -0,0 +1,7 @@
/*TYPESECTION*/
struct CppRef {
int* data;
CppRef() : data(new int(42)) {}
~CppRef() { delete data; data = nullptr; }
void reset() { delete data; data = nullptr; }
};

23
tests/ccgbugs2/t25800.nim Normal file
View File

@@ -0,0 +1,23 @@
discard """
cmd: "nim cpp $file"
action: "compile"
"""
# Bug Report 1: {.importcpp.} on =wasMoved generates invalid preprocessor directive #.
type CppRef* {.importcpp, bycopy, noInit, header: "m25800.h".} = object
proc `=destroy`(x: var CppRef) {.importcpp: "#.~CppRef()".}
proc `=wasMoved`(x: var CppRef) {.importcpp: "#.reset()".}
proc `=copy`(dest: var CppRef; src: CppRef) {.importcpp: "dest = src".}
proc `=sink`(dest: var CppRef; src: CppRef) {.importcpp: "dest = std::move(src)".}
# This triggers =wasMoved when passing to sink parameter
proc consume(x: sink CppRef) = discard
proc test() =
var x: CppRef
consume(move(x)) # =wasMoved MUST be called here after the move
test()

View File

@@ -75,3 +75,19 @@ block: # importc type inheritance
doAssert(cast[cint](b) == 123)
var c = foo(b)
doAssert(cast[cint](c) == 123)
# bug #23765
type
X11[T, E] = object
m: T
B = X11[culonglong, cstring]
S = ref object of RootObj
proc j[T, E](m: X11[T, E]): T = discard
proc n(T: typedesc[SomeUnsignedInt]): X11[T, cstring] = discard
method call(client: S): uint64 {.base.} =
discard j(n(uint64))
var s = S()
discard s.call()

View File

@@ -0,0 +1,10 @@
discard """
matrix: "-d:nimPreviewSlimSystem --warning:StdPrefix:on --warningAsError:StdPrefix:on --import:std/objectdollar"
output: "(a: 23, b: 45)"
"""
type Foo = object
a, b: int
let x = Foo(a: 23, b: 45)
echo x

View File

@@ -176,6 +176,42 @@ block t6462:
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
doAssert s.test() == nil
block concept_with_cint:
# Generic proc matching through concepts with cint should still work
type
FilterMixin[T] = ref object
test: (T) -> bool
trans: (T) -> T
SeqGen[T] = ref object
fil: FilterMixin[T]
WithFilter[T] = concept a
a.fil is FilterMixin[T]
proc test[T](a: WithFilter[T]): (T) -> bool =
a.fil.test
var s = SeqGen[cint](fil: FilterMixin[cint](test: nil, trans: nil))
doAssert s.test() == nil
block concept_with_int:
type
FilterMixin[T] = ref object
test: (T) -> bool
trans: (T) -> T
SeqGen[T] = ref object
fil: FilterMixin[T]
WithFilter[T] = concept a
a.fil is FilterMixin[T]
proc test[T](a: WithFilter[T]): (T) -> bool =
a.fil.test
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
doAssert s.test() == nil
block t6770:

View File

@@ -12,3 +12,51 @@ proc foo =
doAssert m.id == 999
foo()
block:
type Foo = object
a,b,c: int
var dest: Foo
# proc `=wasMoved`(x: var Foo) =
# debugEcho "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
block:
type Foo = object
a,b,c: int
var dest: Foo
proc `=wasMoved`(x: var Foo) =
discard "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
import std/threadpool
block:
type Foo = object
data: string
proc `=wasMoved`(x: var Foo) =
discard
proc work(x: Foo) =
discard
var x = Foo(data: "hello")
spawn work(x)
sync()

View File

@@ -130,3 +130,14 @@ block: # issue #22646
var x: Vec[3, float]
let y = Color(x)
doAssert Vec3[float](y) == x
block: # bug #25697
type MyList = distinct seq[int]
iterator items(x: MyList): lent int {.borrow.}
let s = MyList(@[1, 2, 3])
var count = 0
for item in s:
count += 1
doAssert count == 3, "Expected 3 items, got " & $count

15
tests/errmsgs/t25732.nim Normal file
View File

@@ -0,0 +1,15 @@
discard """
cmd: "nim check --hints:off $file"
action: "reject"
nimout: '''
t25732.nim(15, 32) Error: undeclared identifier: 'a'
t25732.nim(15, 32) Error: expression 'a' has no type (or is ambiguous)
t25732.nim(15, 33) Error: undeclared field: 'b'
t25732.nim(15, 33) Error: undeclared field: '.'
t25732.nim(15, 33) Error: undeclared field: '.'
'''
"""
static: (for f in [0]: discard a.b == f)

View File

@@ -0,0 +1,13 @@
discard """
cmd: "nim check --strings:sso --mm:orc --hints:off $file"
action: "reject"
nimout: '''
tsso_string_index_var.nim(13, 12) Error: expression 's[0]' is immutable, not 'var'
'''
"""
proc passByVar(c: var char) =
c = 'x'
var s = "abc"
passByVar(s[0])

View File

@@ -0,0 +1,61 @@
discard """
targets: "cpp"
matrix: "--mm:arc; --mm:orc; --mm:refc"
output: '''
inner: orig
finally
outer: re:orig
inner-typeless: orig
finally-typeless
outer-typeless: re-tl:orig
no-catch-finally
caught-propagated: prop
'''
"""
# When an `except` handler raises a new exception, the enclosing `finally`
# block must still run before the new exception propagates to the outer
# try.
#
# The C++ backend previously emitted the finally's `catch (...)` as a
# sibling of the user-written catches. C++ does not allow sibling catches
# to catch each other's throws, so a handler-raised exception bypassed the
# finally entirely. The fix wraps the inner try/catch sequence in an
# outer try, so any escaping exception (whether from the body or from a
# handler) is captured before the finally runs.
block typed_except:
try:
try:
raise newException(CatchableError, "orig")
except CatchableError as e:
echo "inner: ", e.msg
raise newException(CatchableError, "re:" & e.msg)
finally:
echo "finally"
except CatchableError as outer:
echo "outer: ", outer.msg
block typeless_except:
try:
try:
raise newException(CatchableError, "orig")
except:
let e = getCurrentException()
echo "inner-typeless: ", e.msg
raise newException(CatchableError, "re-tl:" & e.msg)
finally:
echo "finally-typeless"
except CatchableError as outer:
echo "outer-typeless: ", outer.msg
# try/finally without an except: the body's exception must still propagate
# after the finally runs.
block no_catch_finally:
try:
try:
raise newException(CatchableError, "prop")
finally:
echo "no-catch-finally"
except CatchableError as e:
echo "caught-propagated: ", e.msg

View File

@@ -1,5 +1,5 @@
discard """
matrix: "--mm:refc"
matrix: "--mm:refc; --mm:orc"
targets: "cpp"
output: '''
caught as std::exception

View File

@@ -7,8 +7,10 @@ discard """
1.0
2.0
55
@[1, 2]
'''
"""
import std/strbasics
# Object variant / case object
type
@@ -79,3 +81,14 @@ let x = compute:
echo x
# Crash: bridge.nim(206, 5) `allowEmpty` unexpected nkEmpty [AssertionDefect]
# Bare closure iterator type alias
type IntIter = iterator(): int {.closure.}
proc run(it: IntIter): seq[int] =
result = @[]
for x in it():
result.add(x)
let gen: IntIter = iterator(): int {.closure.} =
yield 1
yield 2
echo run(gen)

View File

@@ -457,3 +457,12 @@ let runes1 = buggyVersion("en") # <-- CRASHES HERE
doAssert runes1.len == runes2.len
# echo "Got ", runes1.len, " runes"
block: # bug #25724
iterator c(): int =
when nimvm: yield 0
else: yield 1
for w in c():
let n = w
(proc() = discard n)()

View File

@@ -4,7 +4,12 @@ js 3.14
7
1
-21550
-21550'''
-21550
none(TT)
()
destroyed
destroyed
'''
"""
# This file tests the JavaScript generator
@@ -56,3 +61,15 @@ proc foo09() =
const y = 86400
echo (x - (y - 1)) div y # Still gives `-21551`
foo09()
import std/options
type TT = object
proc `=destroy`(x: TT) = echo "destroyed"
func test1: Option[TT] = discard
func test2: TT = discard
echo test1() # Crash in JS backend, not crash in C backend
echo test2() # Not crash

View File

@@ -0,0 +1,12 @@
discard """
errormsg: "expression has no address"
"""
iterator foo(x: int): (lent int, lent int) =
yield (x, x + 1)
var x = 12
for i in foo(x):
echo i[0]
echo i[1]

View File

@@ -0,0 +1,8 @@
type
A* = object
discard
B* = object
discard
C* = A | B

View File

@@ -0,0 +1,22 @@
discard """
action: "compile"
"""
import deps/cisaorb
when true:
# These work fine.
discard default(cisaorb.A)
proc f1(x: cisaorb.A) = discard
discard default(cisaorb.B)
proc f2(x: cisaorb.B) = discard
discard default(A)
proc f3(x: A) = discard
discard default(B)
proc f4(x: B) = discard
proc f5(x: C) = discard
proc f6(x: cisaorb.C | C) = discard
proc doesWork(x: A | B) = discard
# Doesn't compile.
proc f(x: cisaorb.C) = discard

View File

@@ -434,3 +434,32 @@ block: # bug #24378
type Win222[T] = typeof("foobar")
doAssert not supportsCopyMem((int, Win222[int]))
doAssert not supportsCopyMem(tuple[a: int, b: Win222[int]])
block: # bug #25789
type
L[T; N: static int] = distinct seq[T]
EPF = distinct L[int, 100]
var e: EPF = EPF(L[int, 100](@[1, 2, 3]))
template classifyGeneric[T](x: T): bool =
when typeof(x) is L:
true
else:
false
template classifyConcrete[T](x: T): bool =
when typeof(x) is L[int, 100]:
true
else:
false
let viaConv = L[int, 100](e)
doAssert $type(viaConv) == "L[system.int, 100]"
doAssert classifyGeneric(viaConv)
doAssert classifyConcrete(viaConv)
let viaDB = distinctBase(e, recursive = false)
doAssert $type(viaDB) == "L[system.int, 100]"
doAssert classifyGeneric(viaDB)
doAssert classifyConcrete(viaDB)

View File

@@ -31,6 +31,9 @@ cmdShortOption key: v value: ''
cmdArgument key: ABC value: ''
cmdShortOption key: j value: '4'
cmdArgument key: ok value: ''
parseopt stdin
cmdShortOption key: j value: '4'
cmdShortOption key: value: ''
'''
joinable: false
"""
@@ -154,3 +157,9 @@ arg 6 ai.len:4 :{a7'b}"""
var n = parseopt.initOptParser("-j4 ok", shortnoVal = {'n'}, longnoVal = @["novalue"])
for kind, key, val in parseopt.getopt(n):
echo kind," key: ", key, " value: '", val, "'"
block: # fix #25738
echo "parseopt stdin"
var p = parseopt.initOptParser("-j4 -", shortNoVal = {'n'})
for kind, key, val in parseopt.getopt(p):
echo kind," key: ", key, " value: '", val, "'"

87
tests/objects/t25627.nim Normal file
View File

@@ -0,0 +1,87 @@
# issue #25627
import std/tables
type
FsoKind = enum
fsoFile
fsoDir
fsoLink
FakeFso = ref object
kind: FsoKind
dirName: string
files: OrderedTable[string, FakeFso]
DirStruct = object
root = FakeFso(kind: fsoDir, dirName: "/")
let dir = DirStruct()
doAssert dir.root.kind == fsoDir
doAssert dir.root.dirName == "/"
doAssert dir.root.files.len == 0
block:
type
Opt[T] = object
when T is ref:
val: T
x: int
else:
val: T
x: string
DefaultOpt = ref object
files: Opt[DefaultOpt]
OptDirStruct = object
root = DefaultOpt()
let dir = OptDirStruct()
doAssert dir.root.files.x is int
block:
type
Opt[T] = object
when T is ref:
x: int
else:
x: string
Foo[T] = object
x: Opt[T]
Nested = ref object
files: Foo[Nested]
let nested = Nested()
doAssert nested.files.x.x is int
block:
type
Foo[T] = object
x = sizeof(T)
Sized = ref object
files: Foo[Sized]
let sized = Sized()
doAssert sized.files.x == sizeof(Sized)
block:
type
Generic[T] = object
t: T
WindowObj = object
svgCache: Generic[SVGSVGElement]
SVGSVGElement = Generic[SVGSVGElementObj]
SVGSVGElementObj = object
proc foo() =
let p: pointer = nil
discard cast[ptr WindowObj](p)
foo()

View File

@@ -833,4 +833,37 @@ proc overloaded[T: object](x: T) =
var v: typeof(val)
overloaded(v)
overloaded(Thing())
overloaded(Thing())
block:
type
Foo = object
x = Bar()
Bar = object
x: int
var f = Foo()
doassert f.x.x == 0
block:
type
Foo = object
x = Bar(x: 55)
Bar = object
x: int
var f = Foo()
doassert f.x.x == 55
block:
type
Bar = object
x: int
Foo = object
x = Bar()
var f = Foo()
doassert f.x.x == 0

34
tests/overload/t25290.nim Normal file
View File

@@ -0,0 +1,34 @@
proc temp(one: int, two: int, three: int) =
discard
template temp(body: untyped): untyped =
body
temp:
proc a(tp: int) =
discard
proc mixedTemp(x: int) =
discard
proc mixedTemp(x: bool) =
discard
template mixedTemp(body: untyped): untyped =
body
# The `bool` proc should win here so `xx` survives
mixedTemp (let xx = 1; true)
discard xx
proc sinkTemp(x: int) =
discard
template sinkTemp(body: untyped): untyped =
discard
# Here the template should win here so `let xy` is sunk into template as AST
sinkTemp (let xy = "template"; xy)
when declared(xy):
{.error: "xy leaked from failed proc candidate".}

View File

@@ -549,3 +549,20 @@ block:
type X {.p.} = object
doAssert foo(X())
block: # typeof() type alias preserves field pragmas
template myFieldPragma {.pragma.}
type Orig = object
x {.myFieldPragma.}: int
var orig: Orig
# Direct typeof alias
type TAlias = typeof(orig)
var a: TAlias
doAssert a.x.hasCustomPragma(myFieldPragma)
# Indirect alias of typeof alias
type TAlias2 = TAlias
var b: TAlias2
doAssert b.x.hasCustomPragma(myFieldPragma)

View File

@@ -0,0 +1,44 @@
# bug #25617
# Ensure that proc types with backend type alias mismatches
# (e.g. uint vs csize_t) are rejected at the Nim level rather
# than producing invalid C code.
discard """
cmd: "nim check --hints:off --warnings:off --errorMax:0 $file"
action: "reject"
nimout: '''
tbackendtypealias.nim(21, 7) Error: type mismatch: got <proc (len: csize_t){.closure.}> but expected 'proc (len: uint){.closure.}'
tbackendtypealias.nim(28, 7) Error: type mismatch: got <proc (len: uint){.closure.}> but expected 'proc (len: csize_t){.closure.}'
'''
"""
block direct_assignment:
# Direct proc variable assignment with backend type alias mismatch
var
a: proc (len: uint)
b: proc (len: csize_t)
c = a
c = b
block direct_assignment_reverse:
var
a: proc (len: csize_t)
b: proc (len: uint)
c = a
c = b
block same_backend_type:
# Same backend type should still work
var
a: proc (len: uint)
b: proc (len: uint)
c = a
c = b
block cint_same_type:
# cint to cint should work
var
a: proc (len: cint)
b: proc (len: cint)
c = a
c = b

View File

@@ -241,3 +241,12 @@ proc main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
# $ separator must be emitted even when the item's string repr is empty
type EmptyStr18583 = object
proc `$`(x: EmptyStr18583): string = ""
block:
var d = [EmptyStr18583(), EmptyStr18583()].toDeque
doAssert $d == "[, ]", "got: " & $d

View File

@@ -104,3 +104,15 @@ template main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583HeapQ = object
proc `$`(x: EmptyStr18583HeapQ): string = ""
proc `<`(a, b: EmptyStr18583HeapQ): bool = false
block:
var h = initHeapQueue[EmptyStr18583HeapQ]()
push(h, EmptyStr18583HeapQ())
push(h, EmptyStr18583HeapQ())
let s = $h
doAssert s == "[, ]", "got: " & s

View File

@@ -287,3 +287,14 @@ template main =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583List = object
proc `$`(x: EmptyStr18583List): string = ""
block:
var L: SinglyLinkedList[EmptyStr18583List]
L.prepend(EmptyStr18583List())
L.prepend(EmptyStr18583List())
let s = $L
doAssert s == "[, ]", "got: " & s

196
tests/stdlib/tnre2.nim Normal file
View File

@@ -0,0 +1,196 @@
import std/[assertions, options, sequtils, strutils, tables]
import std/nre2
block:
let pattern = "[0-9"
doAssertRaises(RegexError): discard re(pattern)
block: # captures
block: # capture bounds are correct
let ex1 = re("([0-9])")
doAssert "1 23".find(ex1).get.matchBounds == 0 .. 0
doAssert "1 23".find(ex1).get.captureBounds[0] == 0 .. 0
doAssert "1 23".find(ex1, 1).get.matchBounds == 2 .. 2
doAssert "1 23".find(ex1, 3).get.matchBounds == 3 .. 3
let ex2 = re("()()()()()()()()()()([0-9])")
doAssert "824".find(ex2).get.captureBounds[0] == 0 .. -1
doAssert "824".find(ex2).get.captureBounds[10] == 0 .. 0
let ex3 = re("([0-9]+)")
doAssert "824".find(ex3).get.captureBounds[0] == 0 .. 2
block: # named captures
let ex1 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)"))
doAssert ex1.get.captures["foo"] == "foo"
doAssert ex1.get.captures["bar"] == "bar"
let ex2 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert "foo" in ex2.get.captureBounds
doAssert ex2.get.captures["foo"] == "foo"
doAssert not ("bar" in ex2.get.captures)
doAssertRaises(KeyError):
discard ex2.get.captures["bar"]
block: # named capture bounds
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert "foo" in ex1.get.captureBounds
doAssert ex1.get.captureBounds["foo"] == 0..2
doAssert not ("bar" in ex1.get.captures)
doAssertRaises(KeyError):
discard ex1.get.captureBounds["bar"]
block: # capture count
let ex1 = re("(?P<foo>foo)(?P<bar>bar)?")
doAssert ex1.captureCount == 2
doAssert ex1.captureNameId == {"foo" : 0, "bar" : 1}.toTable()
block: # named capture table
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert ex1.get.captures.toTable == {"foo" : "foo"}.toTable()
doAssert ex1.get.captureBounds.toTable == {"foo" : 0..2}.toTable()
let ex2 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert ex2.get.captures.toTable == {"foo" : "foo", "bar" : "bar"}.toTable()
block: # capture sequence
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert ex1.get.captures.toSeq == @[some("foo"), none(string)]
doAssert ex1.get.captureBounds.toSeq == @[some(0..2), none(Slice[int])]
doAssert ex1.get.captures.toSeq(some("")) == @[some("foo"), some("")]
let ex2 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)?"))
doAssert ex2.get.captures.toSeq == @[some("foo"), some("bar")]
block: # match
block: # upper bound must be inclusive
doAssert "abc".match(re"abc", endpos = -1) == none(RegexMatch)
doAssert "abc".match(re"abc", endpos = 1) == none(RegexMatch)
doAssert "abc".match(re"abc", endpos = 2) != none(RegexMatch)
block: # match examples
doAssert "abc".match(re"(\w)").get.captures[0] == "a"
doAssert "abc".match(re"(?P<letter>\w)").get.captures["letter"] == "a"
doAssert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
doAssert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
doAssert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
doAssert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
let cap1 = "abc".match(re"(\w)(\w)+").get.captures
doAssert cap1.len == 2
doAssert 0 in cap1
doAssert 1 in cap1
doAssert cap1[0] == "a" and cap1[1] == "c"
doAssert 0 in "abc".match(re"(\w)+").get.captureBounds
block: # match test cases
doAssert "123".match(re"").get.matchBounds == 0 .. -1
let mat1 = "123".match(re"123").get
doAssert mat1.matchBounds == 0 .. 2
doAssert mat1.match == "123"
block: # find
block: # find text
doAssert "3213a".find(re"[a-z]").get.match == "a"
doAssert sequtils.toSeq(findIter("1 2 3 4 5 6 7 8 ", re" ")).mapIt(
it.match
) == @[" ", " ", " ", " ", " ", " ", " ", " "]
block: # find bounds
doAssert sequtils.toSeq(findIter("1 2 3 4 5 ", re" ")).mapIt(
it.matchBounds
) == @[1..1, 3..3, 5..5, 7..7, 9..9]
block: # overlapping find
doAssert "222".findAll(re"22") == @["22"]
doAssert "2222".findAll(re"22") == @["22", "22"]
block: # len 0 find
doAssert "".findAll(re"\ ") == newSeq[string]()
doAssert "".findAll(re"") == @[""]
doAssert "abc".findAll(re"") == @["", "", "", ""]
doAssert "word word".findAll(re"\b") == @["", "", "", ""]
doAssert "word\r\lword".findAll(re"(?m)$") == @["", ""]
doAssert "слово слово".findAll(re"\b") == @["", "", "", ""]
block: # contains
doAssert "abc".contains(re"bc")
doAssert not "abc".contains(re"cd")
doAssert not "abc".contains(re"a", start = 1)
block: # string splitting
block: # splitting strings
doAssert "1 2 3 4 5 6 ".split(re" ") == @["1", "2", "3", "4", "5", "6", ""]
doAssert "1 2 ".split(re(" ")) == @["1", "", "2", "", ""]
doAssert "1 2".split(re(" ")) == @["1", "2"]
doAssert "foo".split(re("foo")) == @["", ""]
doAssert "".split(re"foo") == @[""]
doAssert "9".split(re"\son\s") == @["9"]
block: # captured patterns
doAssert "12".split(re"(\d)") == @["", "1", "", "2", ""]
block: # maxsplit
doAssert "123".split(re"", maxsplit = 2) == @["1", "23"]
doAssert "123".split(re"", maxsplit = 1) == @["123"]
doAssert "123".split(re"", maxsplit = -1) == @["1", "2", "3"]
doAssert "1 2 3".split(re" ", maxsplit = 1) == @["1 2 3"]
doAssert "1 2 3".split(re" ", maxsplit = 2) == @["1", "2 3"]
doAssert "1 2 3".split(re"( )", maxsplit = 2) == @["1", " ", "2 3"]
block: # split with 0-length match
doAssert "12345".split(re("")) == @["1", "2", "3", "4", "5"]
doAssert "".split(re"") == newSeq[string]()
doAssert "word word".split(re"\b") == @["word", " ", "word"]
#doAssert "word\r\lword".split(re"(?m)$") == @["word", "\r\lword"]
doAssert "слово слово".split(re"(\b)") == @["слово", "", " ", "", "слово", ""]
block: # perl split tests
doAssert "forty-two" .split(re"") .join(",") == "f,o,r,t,y,-,t,w,o"
doAssert "forty-two" .split(re"", 3) .join(",") == "f,o,rty-two"
doAssert "split this string" .split(re" ") .join(",") == "split,this,string"
doAssert "split this string" .split(re" ", 2) .join(",") == "split,this string"
doAssert "try$this$string" .split(re"\$") .join(",") == "try,this,string"
doAssert "try$this$string" .split(re"\$", 2) .join(",") == "try,this$string"
doAssert "comma, separated, values" .split(re", ") .join("|") == "comma|separated|values"
doAssert "comma, separated, values" .split(re", ", 2) .join("|") == "comma|separated, values"
doAssert "Perl6::Camelia::Test" .split(re"::") .join(",") == "Perl6,Camelia,Test"
doAssert "Perl6::Camelia::Test" .split(re"::", 2) .join(",") == "Perl6,Camelia::Test"
doAssert "split,me,please" .split(re",") .join("|") == "split|me|please"
doAssert "split,me,please" .split(re",", 2) .join("|") == "split|me,please"
doAssert "Hello World Goodbye Mars".split(re"\s+") .join(",") == "Hello,World,Goodbye,Mars"
doAssert "Hello World Goodbye Mars".split(re"\s+", 3).join(",") == "Hello,World,Goodbye Mars"
doAssert "Hello test" .split(re"(\s+)") .join(",") == "Hello, ,test"
doAssert "this will be split" .split(re" ") .join(",") == "this,will,be,split"
doAssert "this will be split" .split(re" ", 3) .join(",") == "this,will,be split"
doAssert "a.b" .split(re"\.") .join(",") == "a,b"
doAssert "" .split(re"") .len == 0
doAssert ":" .split(re"") .len == 1
block: # start position
doAssert "abc".split(re"", start = 1) == @["b", "c"]
doAssert "abc".split(re"", start = 2) == @["c"]
doAssert "abc".split(re"", start = 3) == newSeq[string]()
doAssert "abc".split(re"^b", start = 1) == @["bc"]
block: # replace
block: # replace with 0-length strings
doAssert "".replace(re"1", proc (v: RegexMatch): string = "1") == ""
doAssert " ".replace(re"", proc (v: RegexMatch): string = "1") == "1 1"
doAssert "".replace(re"", proc (v: RegexMatch): string = "1") == "1"
block: # regular replace
doAssert "123".replace(re"\d", "foo") == "foofoofoo"
doAssert "123".replace(re"(\d)", "$1$1") == "112233"
doAssert "123".replace(re"(\d)(\d)", "$1$2") == "123"
doAssert "123".replace(re"(\d)(\d)", "$#$#") == "123"
doAssert "abcdefghijklm".replace(re"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)", "$12") == "l"
block: # replacing missing captures should throw instead of segfaulting
doAssertRaises(ValueError): discard "ab".replace(re"(a)", "$1$2")
block: # escape strings
block: # escape strings
doAssert "123".escapeRe() == "123"
doAssert "[]".escapeRe() == r"\[\]"
doAssert "()".escapeRe() == r"\(\)"

3
tests/stdlib/tnre2.nims Normal file
View File

@@ -0,0 +1,3 @@
# std/nre2 requires nim-regex and it requires nim-unicodedb
exec("nimble --nimbleDir:build/deps install unicodedb@#head")
exec("nimble --nimbleDir:build/deps install regex@#head")

View File

@@ -259,6 +259,11 @@ block:
doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+")
doAssert(not match("456678", peg"(\letter)+"))
block:
doAssert match("CAFÉ", peg"\i café")
doAssert match("Café", peg"\i café")
doAssert "two cafés: Café and CAFÉ".findAll(peg"\i café").len == 3
doAssert("var1 = key; var2 = key2".replacef(
peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") ==
"var1<-keykey;var2<-key2key2")

View File

@@ -350,3 +350,12 @@ else:
discard"""
a()
# bug: form feed character in comment should not hang renderTree
macro formfeedComment(): untyped =
result = newNimNode(nnkStmtList)
var c = newNimNode(nnkCommentStmt)
c.strVal = "hello\x0Cworld"
result.add c
formfeedComment()

View File

@@ -544,6 +544,12 @@ proc main() =
var x = 5
doAssert fmt"{(x=7;123.456)=:13e}" == "(x=7;123.456)= 1.234560e+02"
doAssert x==7
block: # binary operators in interpolated expressions
let n = 1
doAssert &"{n-1}" == "0"
doAssert fmt"{n-1}" == "0"
block: #curly bracket expressions and tuples
proc formatValue(result: var string; value:Table|bool|JsonNode; specifier:string) = result.add $value

View File

@@ -1,20 +1,24 @@
discard """
matrix: "--mm:refc; --mm:orc"
targets: "c cpp js"
matrix: "--backend:c --mm:refc; --backend:c --mm:orc; --backend:c --mm:orc --strings:sso; --backend:cpp --mm:refc; --backend:cpp --mm:orc; --backend:js --mm:refc; --backend:js --mm:orc"
"""
from std/sequtils import toSeq, map
from std/sugar import `=>`
import std/assertions
const hasNativeSso = defined(nimsso) and
(defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc))
proc tester[T](x: T) =
let test = toSeq(0..4).map(i => newSeq[int]())
doAssert $test == "@[@[], @[], @[], @[], @[]]"
func reverse*(a: string): string =
result = a
for i in 0 ..< a.len div 2:
swap(result[i], result[^(i + 1)])
when not hasNativeSso:
func reverse*(a: string): string =
result = a
for i in 0 ..< a.len div 2:
let j = result.len - i - 1
swap(result[i], result[j])
proc main() =
block: # ..
@@ -94,31 +98,164 @@ proc main() =
block: # bug #7816
tester(1)
block: # bug #14497, reverse
doAssert reverse("hello") == "olleh"
when not hasNativeSso:
block: # bug #14497, reverse
doAssert reverse("hello") == "olleh"
block: # len, high
var a = "ab\0cd"
var b = a.cstring
doAssert a.len == 5
block: # bug #16405
when defined(js):
when nimvm: doAssert b.len == 2
else: doAssert b.len == 5
else: doAssert b.len == 2
doAssert a.high == a.len - 1
doAssert b.high == b.len - 1
when not (hasNativeSso and defined(cpp)):
let b = a.cstring
block: # bug #16405
when defined(js):
when nimvm: doAssert b.len == 2
else: doAssert b.len == 5
else: doAssert b.len == 2
doAssert b.high == b.len - 1
doAssert "".len == 0
doAssert "".high == -1
doAssert "".cstring.len == 0
doAssert "".cstring.high == -1
when not (hasNativeSso and defined(cpp)):
doAssert "".cstring.len == 0
doAssert "".cstring.high == -1
block: # bug #16674
var c: cstring = nil
doAssert c.len == 0
doAssert c.high == -1
block: # setLen, setLenUninit
when hasNativeSso:
const
alwaysAvail = sizeof(uint) - 1
payloadSize = sizeof(uint) + sizeof(pointer) - 2
longStringDataOffset = 3 * sizeof(int)
template rawSlenOf(s: string): int =
int(cast[ptr byte](unsafeAddr s)[])
template inlineDataOf(s: string): ptr UncheckedArray[char] =
cast[ptr UncheckedArray[char]](cast[uint](unsafeAddr s) + 1'u)
template longDataOf(s: string): ptr UncheckedArray[char] =
let ssPtr = cast[ptr tuple[bytes: uint, more: pointer]](unsafeAddr s)
cast[ptr UncheckedArray[char]](
cast[uint](ssPtr.more) + uint(longStringDataOffset))
proc checkStrInternals(s: string; expectedLen: int) =
doAssert s.len == expectedLen, "expected " & $expectedLen & ", got " & $s.len
when nimvm:
discard
else:
when hasNativeSso and not defined(js) and not defined(nimscript):
# SSO
let rawSlen = rawSlenOf(s)
if rawSlen > payloadSize:
doAssert rawSlen == 255
let data = longDataOf(s)
doAssert data[expectedLen] == '\0'
else:
doAssert rawSlen == expectedLen
let data = inlineDataOf(s)
doAssert data[expectedLen] == '\0'
if expectedLen < alwaysAvail:
for i in expectedLen + 1 ..< alwaysAvail:
doAssert data[i] == '\0'
elif defined(UncheckedArray): # skip JS
# string V2
let cs = s.cstring
let arr = cast[ptr UncheckedArray[char]](unsafeAddr cs[0])
doAssert arr[expectedLen] == '\0'
proc makeStr(n: int): string =
result = newStringOfCap(n)
for i in 0..<n:
result.add char(ord('a') + i mod 26)
proc checkSetLenUninit(oldLen, newLen: int; cmpAfter = -1) =
## Verifies `setLenUninit`:
## - preserves the existing prefix
## - updates the string length
## - keeps internal null termination valid for both shrink and growth
##
## `cmpAfter` is used for layouts where trailing zeroed padding affects
## string comparison semantics after the resize.
var s = makeStr(oldLen)
let prefixLen = min(oldLen, newLen)
let prefix = makeStr(prefixLen)
s.setLenUninit(newLen)
s.checkStrInternals(newLen)
doAssert s[0..<prefixLen] == prefix
if newLen <= oldLen:
doAssert s == prefix
if cmpAfter >= 0:
doAssert s < makeStr(cmpAfter)
const numbers = "1234567890"
block setLen:
# Trim to zero and grow past the old end. Must keep the prefix and zero the tail.
var s = numbers
s.setLen(0)
s.checkStrInternals(0)
doAssert s == ""
s = numbers
s.setLen(numbers.len + 1)
s.checkStrInternals(numbers.len + 1)
doAssert s[0..numbers.high] == numbers
doAssert s[numbers.len] == '\0'
block setLenUninit:
# Shared baseline for both SSO and V2: noop, shrink, grow.
checkSetLenUninit(10, 10)
checkSetLenUninit(10, 5)
checkSetLenUninit(10, 11)
block growingWithinBiggerCapacity:
# Strings can reserve spare capacity even for short strings.
# Growing within that capacity must still update len and the trailing zero.
var s = newStringOfCap(10)
s.add("abc")
s.setLenUninit(6)
s.checkStrInternals(6)
doAssert s[0..2] == "abc"
when hasNativeSso:
const
shortLen = alwaysAvail
medLen = payloadSize
longLen = payloadSize + 8
# Staying short and verify short-compare padding after shrink.
checkSetLenUninit(shortLen, shortLen - 1, shortLen)
checkSetLenUninit(shortLen - 2, shortLen - 1)
checkSetLenUninit(shortLen, 0)
# Cross the short/medium boundary in both directions.
checkSetLenUninit(medLen, medLen - 1)
checkSetLenUninit(medLen, alwaysAvail - 1, alwaysAvail)
checkSetLenUninit(alwaysAvail, medLen)
# Cross the inline/long boundary in both directions and cover long growth.
checkSetLenUninit(longLen, longLen - 2)
checkSetLenUninit(longLen, medLen - 1)
checkSetLenUninit(longLen, alwaysAvail - 1, alwaysAvail)
checkSetLenUninit(medLen, longLen)
checkSetLenUninit(longLen, longLen + 10)
checkSetLenUninit(longLen, 0)
when not defined(js) and not defined(nimscript):
# shared long strings must not mutate the original when grown
let src = makeStr(longLen)
var orig = src
var copy = orig
copy.setLenUninit(longLen + 4)
copy.checkStrInternals(longLen + 4)
doAssert orig == src
doAssert copy[0..<longLen] == src
static: main()
main()

7
tests/system/tnimsso.nim Normal file
View File

@@ -0,0 +1,7 @@
discard """
matrix: "--strings:sso --mm:orc"
targets: "c cpp"
"""
var s = "abc"
discard s.cstring

Some files were not shown because too many files have changed in this diff Show More