Files
Nim/changelog.md
Zoom 00d8f66311 std: ossymlinks.expandSymlink via reparse-point parsing (#25701)
This PR implements `expandSymlink` on Windows with POSIX readlink
semantics: it expands exactly one hop and returns the stored link target
without resolving the full chain.

The main design question was whether Windows symlink expansion should be
built on path-finalization APIs such as `GetFinalPathNameByHandleW`, or
on direct reparse-point inspection. Current `expandSymlink` is a
single-hop "what target is stored in this link object?" operation and
most of other ways to resolve symlinks on Windows actually try to answer
the "final true file location" question in various slightly-incompatible
ways.

The full final-path resolution on Windows is substantially more complex
than readlink and is planned as a follow-up.

## Implementation choice

Implements Windows `expandSymlink` by:

- opening the path with `FILE_FLAG_OPEN_REPARSE_POINT`
- calling `DeviceIoControl(FSCTL_GET_REPARSE_POINT)`
- parsing the reparse payload for `IO_REPARSE_TAG_SYMLINK` and
`IO_REPARSE_TAG_MOUNT_POINT`
- decoding the UTF-16 slice referenced by the payload
- returning the stored target

This is the right primitive for the API:
- does not depend on whole-path finalization
- works for both symlinks and junctions
- matches the existing Linux behaviour

`widestrs` changes allow using WideCString views without temporary
allocations.

Windows prohibits symlink creation without admin rights, so,
unfortunately, the tests are conditionally skipped by default. Manually
running `testament` in an admin console is required.

## Behaviour:

- One hop only
- Relative symlink targets are returned unchanged
- Absolute Windows targets are converted from stored NT-style prefixes
to usable Win32 forms when applicable
- Non-links, malformed payloads, and unsupported reparse tags raise
`OSError`

## Future work

Path canonicalization, i.e. "final true file location". Which is, BTW,
different from `absolutePath`, which works on paths only and doesn't hit
the underlying FS. So this needs to be an API extension.

I'd like to follow-up with this when I sort through the docs, for now
you can resolve symlinks in a loop.

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
2026-06-29 18:25:41 +02:00

7.7 KiB

v2.x.x - yyyy-mm-dd

Changes affecting backward compatibility

  • -d:nimPreviewFloatRoundtrip becomes the default. system.addFloat and system.$ now can produce string representations of floating point numbers that are minimal in size and possess round-trip and correct rounding guarantees (via the Dragonbox algorithm). Use -d:nimLegacySprintf to emulate old behaviors.

  • The default parameter of tables.getOrDefault has been renamed to def to avoid conflicts with system.default, so named argument usage for this parameter like getOrDefault(..., default = ...) will have to be changed.

  • With -d:nimPreviewCheckedClose, the close function in the std/syncio module now raises an IO exception in case of an error.

  • Unknown warnings and hints now gives warnings warnUnknownNotes instead of errors.

  • With -d:nimPreviewAsmSemSymbol, backticked symbols are type checked in the asm/emit statements.

  • The bare except: now panics on Defect. Use except Exception: or except Defect: to catch Defect. --legacy:noPanicOnExcept is provided for a transition period.

  • With -d:nimPreviewCStringComparisons, comparsions (<, >, <=, >=) between cstrings switch from reference semantics to value semantics like == and !=.

  • std/parsesql has been moved to a nimble package, use nimble or atlas to install it.

  • With -d:nimPreviewDuplicateModuleError, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Use import foo as foo1 (or other aliases) to avoid collisions.

  • Adds the switch --mangle:nim|cpp, which selects nim or cpp style name mangling when used with debuginfo on, defaults to cpp.

  • The second parameter of succ, pred, inc, and dec in system now accepts SomeInteger (previously Ordinal).

  • Bitshift operators (shl, shr, ashr) now apply bitmasking to the right operand in the C/C++/VM/JS backends.

  • 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

  • Added system.readRawDataStable, a companion to readRawData that returns a raw ptr UncheckedArray[char] into a string's character data which stays valid across moves and copies of the string value. It is available under every string implementation (refc, ARC/ORC and --strings:sso) with the same signature, so code can pin an interior buffer pointer today and be ready for --strings:sso without when declared guards. Under --strings:sso it promotes a small inline string to its heap representation first; under the other implementations the data is already heap-resident, so it is equivalent to readRawData.

  • setutils.symmetricDifference along with its operator version setutils.`-+-` and in-place version setutils.toggle have been added to more efficiently calculate the symmetric difference of bitsets.

  • strutils.multiReplace overload for character set replacements in a single pass. Useful for string sanitation. Follows existing multiReplace semantics.

  • std/files adds:

    • Exports CopyFlag enum and FilePermission type for fine-grained control of file operations
    • New file operation procs with Path support:
      • getFilePermissions, setFilePermissions for managing permissions
      • tryRemoveFile for file deletion
      • copyFile with configurable buffer size and symlink handling
      • copyFileWithPermissions to preserve file attributes
      • copyFileToDir for copying files into directories
  • std/dirs adds:

    • New directory operation procs with Path support:
      • copyDir with special file handling options
      • 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/symlinks.expandSymlink now supports Windows symlinks and junctions with POSIX-like single-hop readlink semantics.

  • std/nre2 is added to replace deprecated NRE.

  • system.typeof adds a new parameter modifierMode to specify how type modifiers are handled.

  • 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 openArrays 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.
  • std/uri: The ? operator now appends query parameters to an existing query string instead of replacing it. Fixes #19782.

Language changes

  • An experimental option --experimental:typeBoundOps has been added that implements the RFC https://github.com/nim-lang/RFCs/issues/380. This makes the behavior of interfaces like hash, $, == etc. more reliable for nominal types across indirect/restricted imports.

    # objs.nim
    import std/hashes
    
    type
      Obj* = object
        x*, y*: int
        z*: string # to be ignored for equality
    
    proc `==`*(a, b: Obj): bool =
      a.x == b.x and a.y == b.y
    
    proc hash*(a: Obj): Hash =
      $!(hash(a.x) &! hash(a.y))
    
    # main.nim
    {.experimental: "typeBoundOps".}
    from objs import Obj # objs.hash, objs.`==` not imported
    import std/tables
    
    var t: Table[Obj, int]
    t[Obj(x: 3, y: 4, z: "debug")] = 34
    echo t[Obj(x: 3, y: 4, z: "ignored")] # 34
    

    See the experimental manual for more information.

Compiler changes

  • Fixed a bug where sizeof(T) inside a typedesc template called from a generic type's when clause would error with "'sizeof' requires '.importc' types to be '.completeStruct'". The issue was that hasValuelessStatics in semtypinst.nim didn't recognize tyTypeDesc(tyGenericParam) as an unresolved generic parameter.

Tool changes

  • Added --raw flag when generating JSON docs to not render markup.
  • Added --stdinfile flag to name of the file used when running program from stdin (defaults to stdinfile.nim)
  • Added --styleCheck:warning flag to treat style check violations as warnings.

Documentation changes

  • Added documentation for the completeStruct pragma in the manual.